@mcpher/gas-fakes 1.2.26 → 1.2.28

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 (51) hide show
  1. package/package.json +1 -1
  2. package/src/cli/setup.js +35 -6
  3. package/src/cli/utils.js +8 -7
  4. package/src/index.js +1 -0
  5. package/src/services/advcalendar/clapis.js +14 -0
  6. package/src/services/advcalendar/fakeadvcalendarcalendarlist.js +30 -0
  7. package/src/services/advcalendar/fakeadvcalendarcalendars.js +25 -0
  8. package/src/services/advcalendar/fakeadvcalendarevents.js +51 -1
  9. package/src/services/calendarapp/app.js +11 -0
  10. package/src/services/calendarapp/fakecalendar.js +328 -0
  11. package/src/services/calendarapp/fakecalendarapp.js +147 -0
  12. package/src/services/calendarapp/fakecalendarevent.js +44 -0
  13. package/src/services/calendarapp/fakecalendareventseries.js +36 -0
  14. package/src/services/calendarapp/fakeeventrecurrence.js +19 -0
  15. package/src/services/enums/calendarenums.js +85 -0
  16. package/src/services/enums/slidesenums.js +1 -1
  17. package/src/services/formapp/fakechoiceitem.js +1 -1
  18. package/src/services/formapp/fakeform.js +4 -3
  19. package/src/services/formapp/fakeformitem.js +1 -2
  20. package/src/services/formapp/fakeformresponse.js +3 -2
  21. package/src/services/formapp/fakelistitem.js +3 -2
  22. package/src/services/scriptapp/behavior.js +56 -1
  23. package/src/services/slidesapp/fakeaffinetransform.js +55 -0
  24. package/src/services/slidesapp/fakeaffinetransformbuilder.js +64 -0
  25. package/src/services/slidesapp/fakeautofit.js +49 -0
  26. package/src/services/slidesapp/fakeautotext.js +29 -0
  27. package/src/services/slidesapp/fakeconnectionsite.js +24 -0
  28. package/src/services/slidesapp/fakelayout.js +38 -0
  29. package/src/services/slidesapp/fakeline.js +195 -0
  30. package/src/services/slidesapp/fakelinefill.js +58 -0
  31. package/src/services/slidesapp/fakelink.js +31 -0
  32. package/src/services/slidesapp/fakemaster.js +28 -0
  33. package/src/services/slidesapp/fakenotespage.js +19 -0
  34. package/src/services/slidesapp/fakepagebackground.js +17 -0
  35. package/src/services/slidesapp/fakepageelement.js +395 -0
  36. package/src/services/slidesapp/fakeparagraph.js +46 -0
  37. package/src/services/slidesapp/fakepoint.js +24 -0
  38. package/src/services/slidesapp/fakepresentation.js +61 -3
  39. package/src/services/slidesapp/fakeshape.js +49 -0
  40. package/src/services/slidesapp/fakeslide.js +232 -0
  41. package/src/services/slidesapp/fakeslidesapp.js +5 -0
  42. package/src/services/slidesapp/faketextrange.js +253 -0
  43. package/src/services/slidesapp/pageelementfactory.js +19 -0
  44. package/src/services/stores/gasflex.js +1 -1
  45. package/src/services/urlfetchapp/app.js +17 -1
  46. package/src/support/sxcalendar.js +61 -0
  47. package/src/support/sxfetch.js +39 -13
  48. package/src/support/sxforms.js +3 -1
  49. package/src/support/syncit.js +16 -2
  50. package/src/support/utils.js +2 -0
  51. package/src/support/workersync/sxfunctions.js +1 -0
@@ -0,0 +1,147 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import * as CalendarEnums from '../enums/calendarenums.js';
3
+ import { newFakeCalendar } from './fakecalendar.js';
4
+
5
+ /**
6
+ * Creates a new FakeCalendarApp instance.
7
+ * @returns {FakeCalendarApp} The new instance.
8
+ */
9
+ export const newFakeCalendarApp = () => {
10
+ return Proxies.guard(new FakeCalendarApp());
11
+ };
12
+
13
+ /**
14
+ * Placeholder for CalendarApp service.
15
+ * @see https://developers.google.com/apps-script/reference/calendar/calendar-app
16
+ */
17
+ export class FakeCalendarApp {
18
+ constructor() {
19
+ // Attach enums
20
+ Object.assign(this, CalendarEnums);
21
+ }
22
+
23
+ createCalendar(name, options = {}) {
24
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'createCalendar');
25
+ this.__checkUsage('write');
26
+ const resource = Calendar.Calendars.insert({ summary: name, ...options });
27
+ if (ScriptApp.__behavior.sandboxMode && resource.id) {
28
+ ScriptApp.__behavior.addCalendarId(resource.id);
29
+ }
30
+ return newFakeCalendar(resource.id, resource);
31
+ }
32
+
33
+ getCalendarById(id) {
34
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'getCalendarById');
35
+ this.__checkUsage('read');
36
+ this.__checkCalendarAccess(id);
37
+ const resource = Calendar.Calendars.get(id);
38
+ return newFakeCalendar(id, resource);
39
+ }
40
+
41
+ getDefaultCalendar() {
42
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'getDefaultCalendar');
43
+ return this.getCalendarById('primary');
44
+ }
45
+
46
+ getCalendarsByName(name) {
47
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'getCalendarsByName');
48
+ this.__checkUsage('read');
49
+ const list = Calendar.CalendarList.list();
50
+ return (list.items || [])
51
+ .filter(item => this.__isCalendarAccessible(item.id))
52
+ .filter(item => item.summary === name)
53
+ .map(item => newFakeCalendar(item.id, item));
54
+ }
55
+
56
+ getAllCalendars() {
57
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'getAllCalendars');
58
+ this.__checkUsage('read');
59
+ const list = Calendar.CalendarList.list();
60
+ return (list.items || [])
61
+ .filter(item => this.__isCalendarAccessible(item.id))
62
+ .map(item => newFakeCalendar(item.id, item));
63
+ }
64
+
65
+ getAllOwnedCalendars() {
66
+ ScriptApp.__behavior.checkMethod('CalendarApp', 'getAllOwnedCalendars');
67
+ this.__checkUsage('read');
68
+ const list = Calendar.CalendarList.list();
69
+ return (list.items || [])
70
+ .filter(item => this.__isCalendarAccessible(item.id))
71
+ .filter(item => item.accessRole === 'owner')
72
+ .map(item => newFakeCalendar(item.id, item));
73
+ }
74
+
75
+ __isCalendarAccessible(calendarId) {
76
+ try {
77
+ this.__checkCalendarAccess(calendarId);
78
+ return true;
79
+ } catch (e) {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ __checkCalendarAccess(calendarId) {
85
+ const behavior = ScriptApp.__behavior;
86
+ if (!behavior.sandboxMode) return true;
87
+
88
+ // 1. Session check - calendars created in this session are always accessible
89
+ if (behavior.isKnownCalendar(calendarId)) return true;
90
+
91
+ // 2. Primary calendar is always accessible
92
+ if (calendarId === 'primary') return true;
93
+
94
+ // 3. Whitelist check
95
+ const settings = behavior.sandboxService.CalendarApp;
96
+ const whitelist = settings && settings.calendarWhitelist;
97
+
98
+ if (!whitelist) {
99
+ throw new Error(`Access to calendar ${calendarId} is denied. No calendar whitelist configured.`);
100
+ }
101
+
102
+ // Get calendar details to check name
103
+ const calendar = Calendar.Calendars.get(calendarId);
104
+ const calendarName = calendar.summary;
105
+
106
+ // Check if calendar name is in whitelist with read permission
107
+ const entry = whitelist.find(item => item.name === calendarName);
108
+ if (entry && entry.read) {
109
+ return true;
110
+ }
111
+
112
+ throw new Error(`Access to calendar "${calendarName}" (${calendarId}) is denied by sandbox rules`);
113
+ }
114
+
115
+ __checkUsage(type) {
116
+ const serviceName = 'CalendarApp';
117
+ const behavior = ScriptApp.__behavior;
118
+ if (behavior.sandboxMode) {
119
+ const settings = behavior.sandboxService[serviceName];
120
+ let limit = settings && settings.usageLimit;
121
+ if (limit) {
122
+ if (typeof limit === 'number') {
123
+ const total = (settings.usageCount.read || 0) + (settings.usageCount.write || 0) + (settings.usageCount.trash || 0);
124
+ if (total >= limit) {
125
+ throw new Error(`Calendar total usage limit of ${limit} exceeded`);
126
+ }
127
+ settings.incrementUsage(type);
128
+ return;
129
+ }
130
+
131
+ let specificLimit = limit[type];
132
+ if (specificLimit !== undefined) {
133
+ const current = settings.usageCount[type] || 0;
134
+ if (current >= specificLimit) {
135
+ throw new Error(`Calendar ${type} usage limit of ${specificLimit} exceeded`);
136
+ }
137
+ settings.incrementUsage(type);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // Common pattern in gas-fakes for internal use
144
+ __addAllowed(id) {
145
+ // Shared logic with other services to track created resources
146
+ }
147
+ }
@@ -0,0 +1,44 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+
3
+ export const newFakeCalendarEvent = (...args) => {
4
+ return Proxies.guard(new FakeCalendarEvent(...args));
5
+ };
6
+
7
+ /**
8
+ * Represents a calendar event.
9
+ * @see https://developers.google.com/apps-script/reference/calendar/calendar-event
10
+ */
11
+ export class FakeCalendarEvent {
12
+ /**
13
+ * @param {string} calendarId The calendar ID.
14
+ * @param {object} resource The underlying Event resource (Advanced).
15
+ */
16
+ constructor(calendarId, resource) {
17
+ this.__calendarId = calendarId;
18
+ this.__id = resource.id;
19
+ this.__iCalUID = resource.iCalUID;
20
+ }
21
+
22
+ get __resource() {
23
+ // Try to get by ID
24
+ try {
25
+ return Calendar.Events.get(this.__calendarId, this.__id);
26
+ } catch (e) {
27
+ // Fallback: search by iCalUID if main ID fails (though usually id is reliable for get)
28
+ // or if the event was deleted?
29
+ throw new Error(`Event with ID ${this.__id} not found in calendar ${this.__calendarId}`);
30
+ }
31
+ }
32
+
33
+ getId() {
34
+ return this.__resource.iCalUID || this.__resource.id;
35
+ }
36
+
37
+ getTitle() {
38
+ return this.__resource.summary || '';
39
+ }
40
+
41
+ toString() {
42
+ return 'CalendarEvent';
43
+ }
44
+ }
@@ -0,0 +1,36 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+
3
+ export const newFakeCalendarEventSeries = (...args) => {
4
+ return Proxies.guard(new FakeCalendarEventSeries(...args));
5
+ };
6
+
7
+ /**
8
+ * Represents a series of events (a recurring event).
9
+ * @see https://developers.google.com/apps-script/reference/calendar/calendar-event-series
10
+ */
11
+ export class FakeCalendarEventSeries {
12
+ /**
13
+ * @param {string} calendarId The calendar ID.
14
+ * @param {object} resource The underlying Event resource (Advanced).
15
+ */
16
+ constructor(calendarId, resource) {
17
+ this.__calendarId = calendarId;
18
+ this.__id = resource.id;
19
+ }
20
+
21
+ get __resource() {
22
+ try {
23
+ return Calendar.Events.get(this.__calendarId, this.__id);
24
+ } catch (e) {
25
+ throw new Error(`Event Series with ID ${this.__id} not found in calendar ${this.__calendarId}`);
26
+ }
27
+ }
28
+
29
+ getId() {
30
+ return this.__resource.iCalUID || this.__resource.id;
31
+ }
32
+
33
+ toString() {
34
+ return 'CalendarEventSeries';
35
+ }
36
+ }
@@ -0,0 +1,19 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+
3
+ export const newFakeEventRecurrence = (...args) => {
4
+ return Proxies.guard(new FakeEventRecurrence(...args));
5
+ };
6
+
7
+ /**
8
+ * Represents the recurrence settings for an event series.
9
+ * @see https://developers.google.com/apps-script/reference/calendar/event-recurrence
10
+ */
11
+ export class FakeEventRecurrence {
12
+ constructor() {
13
+ // TODO: Implement recurrence rules storage
14
+ }
15
+
16
+ toString() {
17
+ return 'EventRecurrence';
18
+ }
19
+ }
@@ -0,0 +1,85 @@
1
+ import { newFakeGasenum} from "@mcpher/fake-gasenum";
2
+ export const Color = newFakeGasenum([
3
+ "BLUE",
4
+ "BROWN",
5
+ "CHARCOAL",
6
+ "CHESTNUT",
7
+ "GRAY",
8
+ "GREEN",
9
+ "INDIGO",
10
+ "LIME",
11
+ "MUSTARD",
12
+ "OLIVE",
13
+ "ORANGE",
14
+ "PINK",
15
+ "PLUM",
16
+ "PURPLE",
17
+ "RED",
18
+ "RED_ORANGE",
19
+ "SEA_BLUE",
20
+ "SLATE",
21
+ "TEAL",
22
+ "TURQOISE",
23
+ "YELLOW"
24
+ ])
25
+ export const EventColor = newFakeGasenum([
26
+ "BLUE",
27
+ "CYAN",
28
+ "GRAY",
29
+ "GREEN",
30
+ "MAUVE",
31
+ "ORANGE",
32
+ "PALE_BLUE",
33
+ "PALE_GREEN",
34
+ "PALE_RED",
35
+ "RED",
36
+ "YELLOW"
37
+ ])
38
+ export const EventTransparency = newFakeGasenum([
39
+ "OPAQUE",
40
+ "TRANSPARENT"
41
+ ])
42
+ export const EventType = newFakeGasenum([
43
+ "DEFAULT",
44
+ "BIRTHDAY",
45
+ "FOCUS_TIME",
46
+ "FROM_GMAIL",
47
+ "OUT_OF_OFFICE",
48
+ "WORKING_LOCATION"
49
+ ])
50
+ export const GuestStatus = newFakeGasenum([
51
+ "INVITED",
52
+ "MAYBE",
53
+ "NO",
54
+ "OWNER",
55
+ "YES"
56
+ ])
57
+ export const Month = newFakeGasenum([
58
+ "JANUARY",
59
+ "FEBRUARY",
60
+ "MARCH",
61
+ "APRIL",
62
+ "MAY",
63
+ "JUNE",
64
+ "JULY",
65
+ "AUGUST",
66
+ "SEPTEMBER",
67
+ "OCTOBER",
68
+ "NOVEMBER",
69
+ "DECEMBER"
70
+ ])
71
+ export const Visibility = newFakeGasenum([
72
+ "CONFIDENTIAL",
73
+ "DEFAULT",
74
+ "PRIVATE",
75
+ "PUBLIC"
76
+ ])
77
+ export const Weekday = newFakeGasenum([
78
+ "SUNDAY",
79
+ "MONDAY",
80
+ "TUESDAY",
81
+ "WEDNESDAY",
82
+ "THURSDAY",
83
+ "FRIDAY",
84
+ "SATURDAY"
85
+ ])
@@ -1,4 +1,4 @@
1
- import { newFakeGasenum} from "@mcpher/fake-gasenum";
1
+ import { newFakeGasenum } from "@mcpher/fake-gasenum";
2
2
 
3
3
  export const AlignmentPosition = newFakeGasenum([
4
4
  "CENTER",
@@ -45,7 +45,7 @@ export class FakeChoiceItem extends newFakeFormItem().constructor {
45
45
 
46
46
  const updateRequest = Forms.newRequest().setUpdateItem({
47
47
  item: {
48
- itemId: this.getId(),
48
+ itemId: this.__itemId,
49
49
  questionItem: {
50
50
  question: {
51
51
  choiceQuestion: choiceQuestion,
@@ -13,7 +13,7 @@ import { newFakeListItem } from './fakelistitem.js';
13
13
  import { newFakePageBreakItem } from './fakepagebreakitem.js';
14
14
  import { newFakeTextItem } from './faketextitem.js';
15
15
  import { signatureArgs } from '../../support/helpers.js';
16
- import {Utils} from '../../support/utils.js';
16
+ import { Utils } from '../../support/utils.js';
17
17
  const { is } = Utils
18
18
  export const newFakeForm = (...args) => {
19
19
  return Proxies.guard(new FakeForm(...args));
@@ -53,7 +53,7 @@ export class FakeForm {
53
53
  return this;
54
54
  }
55
55
 
56
-
56
+
57
57
  saveAndClose() {
58
58
  // this is a no-op in fake environment since it is stateless
59
59
  }
@@ -298,6 +298,7 @@ export class FakeForm {
298
298
  if (!this.__resource.items) {
299
299
  return null;
300
300
  }
301
+ const hexId = Utils.toHex(id);
301
302
  const isKnownItem = (id, item) => {
302
303
  if (item.itemId === id) return true;
303
304
  if (item.questionItem?.question?.questionId === id) return true;
@@ -307,7 +308,7 @@ export class FakeForm {
307
308
  return found
308
309
  }
309
310
 
310
- const itemResource = this.__resource.items.find((item) => isKnownItem(id, item));
311
+ const itemResource = this.__resource.items.find((item) => isKnownItem(hexId, item));
311
312
 
312
313
  if (!itemResource) {
313
314
  return null;
@@ -114,8 +114,7 @@ export class FakeFormItem {
114
114
  }
115
115
 
116
116
  getId() {
117
- // Live Apps Script returns IDs as decimal numbers, not the hex strings from the API.
118
- return parseInt(this.__itemId, 16); // Convert to decimal
117
+ return Utils.fromHex(this.__itemId);
119
118
  }
120
119
 
121
120
  getIndex() {
@@ -1,5 +1,6 @@
1
1
  import { Proxies } from '../../support/proxies.js';
2
2
  import { newFakeItemResponse } from './fakeitemresponse.js';
3
+ import { Utils } from '../../support/utils.js';
3
4
 
4
5
  export const newFakeFormResponse = (...args) => {
5
6
  return Proxies.guard(new FakeFormResponse(...args));
@@ -33,7 +34,7 @@ export class FakeFormResponse {
33
34
  * @returns {number} the unique hex ID converted to a decimal number
34
35
  */
35
36
  getId() {
36
- return parseInt(this.__resource.responseId, 16);
37
+ return Utils.fromHex(this.__resource.responseId);
37
38
  }
38
39
 
39
40
  /**
@@ -59,7 +60,7 @@ export class FakeFormResponse {
59
60
  const groupedAnswers = new Map();
60
61
 
61
62
  for (const [questionId, answer] of Object.entries(this.__resource.answers)) {
62
- const item = this.__form.getItemById(questionId);
63
+ const item = this.__form.getItemById(Utils.fromHex(questionId));
63
64
 
64
65
  if (item) {
65
66
  const itemId = item.getId(); // Use the unique item ID as the key.
@@ -3,6 +3,7 @@ import { FakeFormItem } from './fakeformitem.js';
3
3
  import { newFakeChoice } from './fakechoice.js';
4
4
  import { registerFormItem } from './formitemregistry.js';
5
5
  import { ItemType } from '../enums/formsenums.js';
6
+ import { Utils } from '../../support/utils.js';
6
7
 
7
8
  export const newFakeListItem = (...args) => {
8
9
  return Proxies.guard(new FakeListItem(...args));
@@ -56,7 +57,7 @@ export class FakeListItem extends FakeFormItem {
56
57
  // Translate from API format back to Apps Script format.
57
58
  if (option.goToSectionId) {
58
59
  navType = 'GO_TO_PAGE';
59
- pageId = option.goToSectionId;
60
+ pageId = Utils.fromHex(option.goToSectionId);
60
61
  } else if (option.goToAction) {
61
62
  switch (option.goToAction) {
62
63
  case 'NEXT_SECTION': navType = 'CONTINUE'; break;
@@ -85,7 +86,7 @@ export class FakeListItem extends FakeFormItem {
85
86
  case 'GO_TO_PAGE':
86
87
  // For navigating to a specific page, you ONLY set the goToSectionId.
87
88
  // The pageId is the itemId of the PageBreakItem.
88
- option.goToSectionId = choice.__pageId;
89
+ option.goToSectionId = Utils.toHex(choice.__pageId);
89
90
  break;
90
91
  case 'CONTINUE':
91
92
  option.goToAction = 'NEXT_SECTION';
@@ -17,6 +17,7 @@ const serviceModel = {
17
17
  methodWhitelist: null,
18
18
  emailWhitelist: null,
19
19
  labelWhitelist: null,
20
+ calendarWhitelist: null,
20
21
  usageLimit: null,
21
22
  usageCount: 0
22
23
  }
@@ -169,6 +170,17 @@ class FakeSandboxService {
169
170
  return this.__state.labelWhitelist
170
171
  }
171
172
 
173
+ set calendarWhitelist(value) {
174
+ if (!is.null(value)) {
175
+ checkArgs(value, "array")
176
+ // We expect objects like { name: 'calendar-name', read?: boolean, write?: boolean, delete?: boolean }
177
+ }
178
+ this.__state.calendarWhitelist = value
179
+ }
180
+ get calendarWhitelist() {
181
+ return this.__state.calendarWhitelist
182
+ }
183
+
172
184
  set usageLimit(value) {
173
185
  if (!is.null(value)) {
174
186
  // expect object with read, write, trash keys optionally
@@ -211,6 +223,11 @@ class FakeSandboxService {
211
223
  return this.__state.usageCount[type];
212
224
  }
213
225
 
226
+ resetUsageCount() {
227
+ this.__state.usageCount = { read: 0, write: 0, trash: 0, send: 0 };
228
+ return this;
229
+ }
230
+
214
231
  set enabled(value) {
215
232
  this.__state.enabled = checkArgs(value)
216
233
  }
@@ -236,6 +253,7 @@ class FakeBehavior {
236
253
  // key is the file id
237
254
  this.__createdIds = new Set();
238
255
  this.__createdGmailIds = new Set();
256
+ this.__createdCalendarIds = new Set();
239
257
  // in sandbox mode we only allow access to files created in this instance
240
258
  // this is to emulate the behavior of a drive.file scope
241
259
  this.__sandboxMode = false;
@@ -347,6 +365,18 @@ class FakeBehavior {
347
365
  }
348
366
  return id
349
367
  }
368
+ addCalendarId(id) {
369
+ if (this.sandboxMode) {
370
+ if (!is.nonEmptyString(id)) {
371
+ throw new Error(`Invalid sandbox id parameter (${id}) - must be a non-empty string`);
372
+ }
373
+ if (!this.isKnownCalendar(id)) {
374
+ slogger.log(`...adding calendar id ${id} to sandbox allowed list`);
375
+ this.__createdCalendarIds.add(id);
376
+ }
377
+ }
378
+ return id
379
+ }
350
380
  isAccessible(id, serviceName, accessType = 'read') {
351
381
  if (this.sandboxMode && !is.nonEmptyString(id)) {
352
382
  throw new Error(`Invalid sandbox id parameter (${id}) - must be a non-empty string`);
@@ -510,7 +540,29 @@ class FakeBehavior {
510
540
  slogger.log('...skipping cleaning up sandbox files (Gmail)');
511
541
  }
512
542
 
513
- slogger.log(`...trashed ${trashed.length} sandboxed files and ${trashedGmail.length} gmail items`);
543
+ // Clean up Calendar artifacts
544
+ let trashedCalendars = [];
545
+ const calendarSettings = this.sandboxService.CalendarApp;
546
+ const calendarCleanup = calendarSettings && calendarSettings.cleanup;
547
+
548
+ if (calendarCleanup) {
549
+ trashedCalendars = Array.from(this.__createdCalendarIds).reduce((acc, id) => {
550
+ try {
551
+ // Delete calendar
552
+ Calendar.Calendars.delete(id);
553
+ slogger.log(`...deleted calendar ${id}`);
554
+ acc.push(id);
555
+ } catch (e) {
556
+ slogger.log(`...failed to delete calendar ${id}: ${e.message}`);
557
+ }
558
+ return acc;
559
+ }, []);
560
+ this.__createdCalendarIds.clear();
561
+ } else {
562
+ slogger.log('...skipping cleaning up sandbox calendars');
563
+ }
564
+
565
+ slogger.log(`...trashed ${trashed.length} sandboxed files, ${trashedGmail.length} gmail items, and ${trashedCalendars.length} calendars`);
514
566
  return trashed;
515
567
  }
516
568
  isKnown(id) {
@@ -519,4 +571,7 @@ class FakeBehavior {
519
571
  isKnownGmail(id) {
520
572
  return this.__createdGmailIds.has(id);
521
573
  }
574
+ isKnownCalendar(id) {
575
+ return this.__createdCalendarIds.has(id);
576
+ }
522
577
  }
@@ -0,0 +1,55 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeAffineTransformBuilder } from './fakeaffinetransformbuilder.js';
3
+
4
+ export const newFakeAffineTransform = (...args) => {
5
+ return Proxies.guard(new FakeAffineTransform(...args));
6
+ };
7
+
8
+ export class FakeAffineTransform {
9
+ constructor(scaleX, shearY, shearX, scaleY, translateX, translateY) {
10
+ this.__scaleX = scaleX;
11
+ this.__shearY = shearY;
12
+ this.__shearX = shearX;
13
+ this.__scaleY = scaleY;
14
+ this.__translateX = translateX;
15
+ this.__translateY = translateY;
16
+ }
17
+
18
+ getScaleX() {
19
+ return this.__scaleX;
20
+ }
21
+
22
+ getScaleY() {
23
+ return this.__scaleY;
24
+ }
25
+
26
+ getShearX() {
27
+ return this.__shearX;
28
+ }
29
+
30
+ getShearY() {
31
+ return this.__shearY;
32
+ }
33
+
34
+ getTranslateX() {
35
+ return this.__translateX;
36
+ }
37
+
38
+ getTranslateY() {
39
+ return this.__translateY;
40
+ }
41
+
42
+ toBuilder() {
43
+ return newFakeAffineTransformBuilder()
44
+ .setScaleX(this.__scaleX)
45
+ .setShearY(this.__shearY)
46
+ .setShearX(this.__shearX)
47
+ .setScaleY(this.__scaleY)
48
+ .setTranslateX(this.__translateX)
49
+ .setTranslateY(this.__translateY);
50
+ }
51
+
52
+ toString() {
53
+ return 'AffineTransform';
54
+ }
55
+ }
@@ -0,0 +1,64 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeAffineTransform } from './fakeaffinetransform.js';
3
+
4
+ export const newFakeAffineTransformBuilder = (...args) => {
5
+ return Proxies.guard(new FakeAffineTransformBuilder(...args));
6
+ };
7
+
8
+ export class FakeAffineTransformBuilder {
9
+ constructor() {
10
+ // Defaults? Documentation doesn't explicitly specify, but identity matrix is reasonable.
11
+ // scaleX=1, scaleY=1, others 0.
12
+ this.__scaleX = 1;
13
+ this.__shearY = 0;
14
+ this.__shearX = 0;
15
+ this.__scaleY = 1;
16
+ this.__translateX = 0;
17
+ this.__translateY = 0;
18
+ }
19
+
20
+ build() {
21
+ return newFakeAffineTransform(
22
+ this.__scaleX,
23
+ this.__shearY,
24
+ this.__shearX,
25
+ this.__scaleY,
26
+ this.__translateX,
27
+ this.__translateY
28
+ );
29
+ }
30
+
31
+ setScaleX(scaleX) {
32
+ this.__scaleX = scaleX;
33
+ return this;
34
+ }
35
+
36
+ setScaleY(scaleY) {
37
+ this.__scaleY = scaleY;
38
+ return this;
39
+ }
40
+
41
+ setShearX(shearX) {
42
+ this.__shearX = shearX;
43
+ return this;
44
+ }
45
+
46
+ setShearY(shearY) {
47
+ this.__shearY = shearY;
48
+ return this;
49
+ }
50
+
51
+ setTranslateX(translateX) {
52
+ this.__translateX = translateX;
53
+ return this;
54
+ }
55
+
56
+ setTranslateY(translateY) {
57
+ this.__translateY = translateY;
58
+ return this;
59
+ }
60
+
61
+ toString() {
62
+ return 'AffineTransformBuilder';
63
+ }
64
+ }