@mcpher/gas-fakes 2.3.10 → 2.3.11

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/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "name": "@mcpher/gas-fakes",
42
42
  "author": "bruce mcpherson",
43
- "version": "2.3.10",
43
+ "version": "2.3.11",
44
44
  "license": "MIT",
45
45
  "main": "main.js",
46
46
  "description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
@@ -286,3 +286,30 @@ export const updateParagraphStyle = (element, paragraphStyle, fields) => {
286
286
  shadow.refresh();
287
287
  return element;
288
288
  };
289
+
290
+ /**
291
+ * Updates the text style for a given element or range within it.
292
+ * @param {import('./fakeelement.js').FakeElement} element The element whose text style to update.
293
+ * @param {GoogleAppsScript.Document.TextStyle} textStyle The style object to apply.
294
+ * @param {string} fields The comma-separated string of field names to update.
295
+ * @param {object} [range] Optional specific range. Defaults to the element's range.
296
+ * @returns {import('./fakeelement.js').FakeElement} The element, for chaining.
297
+ */
298
+ export const updateTextStyle = (element, textStyle, fields, range = null) => {
299
+ const shadow = element.shadowDocument;
300
+ const item = element.__elementMapItem;
301
+ const updateRange = range || {
302
+ startIndex: item.startIndex,
303
+ endIndex: item.endIndex,
304
+ segmentId: shadow.__segmentId,
305
+ tabId: shadow.__tabId,
306
+ };
307
+
308
+ const requests = [{
309
+ updateTextStyle: { range: updateRange, textStyle, fields },
310
+ }];
311
+
312
+ Docs.Documents.batchUpdate({ requests }, shadow.getId());
313
+ shadow.refresh();
314
+ return element;
315
+ };
@@ -96,6 +96,14 @@ export class FakeElement {
96
96
  });
97
97
  }
98
98
 
99
+ /**
100
+ * Gets the shadow document manager associated with this element's structure.
101
+ * @type {import('./shadowdocument.js').ShadowDocument | null}
102
+ */
103
+ get shadowDocument() {
104
+ return this.__shadowDocument;
105
+ }
106
+
99
107
  get __structure() {
100
108
  if (this.__isDetached) return null;
101
109
  return this.__shadowDocument.structure;
@@ -4,6 +4,7 @@ import { Utils } from '../../support/utils.js';
4
4
  import { imageOptions } from './elementoptions.js';
5
5
  import { FakeContainerElement } from './fakecontainerelement.js';
6
6
  import { registerElement } from './elementRegistry.js';
7
+ import { newFakeText } from './faketext.js';
7
8
  import { appendText, addPositionedImage, appendImage, insertImage } from './appenderhelpers.js';
8
9
  import { getText as getTextHelper, getAttributes as getAttributesHelper, updateParagraphStyle } from './elementhelpers.js';
9
10
 
@@ -32,6 +33,19 @@ export class FakeParagraph extends FakeContainerElement {
32
33
  return getTextHelper(this);
33
34
  }
34
35
 
36
+ /**
37
+ * Returns the contents of the paragraph as a Text element.
38
+ * @returns {GoogleAppsScript.Document.Text} The text element.
39
+ */
40
+ editAsText() {
41
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.editAsText');
42
+ if (nargs !== 0) matchThrow();
43
+ // In gas-fakes, we can return a FakeText that points to the same underlying item.
44
+ // The getText and updateTextStyle methods will work correctly because they
45
+ // handle both PARAGRAPH and TEXT items or ranges.
46
+ return newFakeText(this.__shadowDocument, this.__name);
47
+ }
48
+
35
49
  appendText(text) {
36
50
  const { nargs, matchThrow } = signatureArgs(arguments, 'Paragraph.appendText');
37
51
  if (nargs !== 1) matchThrow();
@@ -1,9 +1,15 @@
1
1
  import { Proxies } from '../../support/proxies.js';
2
+ import { signatureArgs } from '../../support/helpers.js';
3
+ import { Utils } from '../../support/utils.js';
2
4
  import { FakeElement } from './fakeelement.js';
3
5
  import { registerElement } from './elementRegistry.js';
6
+ import { getAttributes as getAttributesHelper, updateTextStyle, getText as getTextHelper } from './elementhelpers.js';
7
+
8
+ const { is } = Utils;
4
9
 
5
10
  /**
6
11
  * A fake implementation of the Text class for DocumentApp.
12
+ * @implements {GoogleAppsScript.Document.Text}
7
13
  * @see https://developers.google.com/apps-script/reference/document/text
8
14
  */
9
15
  class FakeText extends FakeElement {
@@ -16,9 +22,173 @@ class FakeText extends FakeElement {
16
22
  * @returns {string} The text contents.
17
23
  */
18
24
  getText() {
19
- const item = this.__elementMapItem;
20
- // A Text element corresponds to a textRun in the API.
21
- return item.textRun?.content || '';
25
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getText');
26
+ if (nargs !== 0) matchThrow();
27
+ return getTextHelper(this);
28
+ }
29
+
30
+ getAttributes() {
31
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getAttributes');
32
+ if (nargs !== 0) matchThrow();
33
+ return getAttributesHelper(this);
34
+ }
35
+
36
+ setAttributes(attributes) {
37
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setAttributes');
38
+ if (nargs !== 1 || !is.object(attributes)) matchThrow();
39
+
40
+ const textStyle = {};
41
+ const textFields = [];
42
+ const Attribute = DocumentApp.Attribute;
43
+
44
+ const colorToRgb = (hex) => {
45
+ if (!hex || !hex.startsWith('#') || hex.length !== 7) return null;
46
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
47
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
48
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
49
+ return { red: r, green: g, blue: b };
50
+ };
51
+
52
+ for (const key in attributes) {
53
+ const value = attributes[key];
54
+ // Note: GAS allows null to clear some attributes, but let's handle the basics first.
55
+ switch (String(key)) {
56
+ case String(Attribute.BACKGROUND_COLOR): textStyle.backgroundColor = { color: { rgbColor: colorToRgb(value) } }; textFields.push('backgroundColor'); break;
57
+ case String(Attribute.BOLD): textStyle.bold = value; textFields.push('bold'); break;
58
+ case String(Attribute.FONT_FAMILY): textStyle.weightedFontFamily = { fontFamily: value }; textFields.push('weightedFontFamily'); break;
59
+ case String(Attribute.FONT_SIZE): textStyle.fontSize = { magnitude: value, unit: 'PT' }; textFields.push('fontSize'); break;
60
+ case String(Attribute.FOREGROUND_COLOR): textStyle.foregroundColor = { color: { rgbColor: colorToRgb(value) } }; textFields.push('foregroundColor'); break;
61
+ case String(Attribute.ITALIC): textStyle.italic = value; textFields.push('italic'); break;
62
+ case String(Attribute.LINK_URL): textStyle.link = { url: value }; textFields.push('link'); break;
63
+ case String(Attribute.STRIKETHROUGH): textStyle.strikethrough = value; textFields.push('strikethrough'); break;
64
+ case String(Attribute.UNDERLINE): textStyle.underline = value; textFields.push('underline'); break;
65
+ }
66
+ }
67
+
68
+ if (textFields.length === 0) return this;
69
+ return updateTextStyle(this, textStyle, textFields.join(','));
70
+ }
71
+
72
+ setBold(bold) {
73
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setBold');
74
+ if (nargs !== 1 || !is.boolean(bold)) matchThrow();
75
+ return updateTextStyle(this, { bold }, 'bold');
76
+ }
77
+
78
+ isBold() {
79
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.isBold');
80
+ if (nargs !== 0) matchThrow();
81
+ return !!this.getAttributes()[DocumentApp.Attribute.BOLD];
82
+ }
83
+
84
+ setItalic(italic) {
85
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setItalic');
86
+ if (nargs !== 1 || !is.boolean(italic)) matchThrow();
87
+ return updateTextStyle(this, { italic }, 'italic');
88
+ }
89
+
90
+ isItalic() {
91
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.isItalic');
92
+ if (nargs !== 0) matchThrow();
93
+ return !!this.getAttributes()[DocumentApp.Attribute.ITALIC];
94
+ }
95
+
96
+ setUnderline(underline) {
97
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setUnderline');
98
+ if (nargs !== 1 || !is.boolean(underline)) matchThrow();
99
+ return updateTextStyle(this, { underline }, 'underline');
100
+ }
101
+
102
+ isUnderline() {
103
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.isUnderline');
104
+ if (nargs !== 0) matchThrow();
105
+ return !!this.getAttributes()[DocumentApp.Attribute.UNDERLINE];
106
+ }
107
+
108
+ setFontFamily(fontFamily) {
109
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setFontFamily');
110
+ if (nargs !== 1 || !is.string(fontFamily)) matchThrow();
111
+ return updateTextStyle(this, { weightedFontFamily: { fontFamily } }, 'weightedFontFamily');
112
+ }
113
+
114
+ getFontFamily() {
115
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getFontFamily');
116
+ if (nargs !== 0) matchThrow();
117
+ return this.getAttributes()[DocumentApp.Attribute.FONT_FAMILY];
118
+ }
119
+
120
+ setFontSize(fontSize) {
121
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setFontSize');
122
+ if (nargs !== 1 || !is.number(fontSize)) matchThrow();
123
+ return updateTextStyle(this, { fontSize: { magnitude: fontSize, unit: 'PT' } }, 'fontSize');
124
+ }
125
+
126
+ getFontSize() {
127
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getFontSize');
128
+ if (nargs !== 0) matchThrow();
129
+ return this.getAttributes()[DocumentApp.Attribute.FONT_SIZE];
130
+ }
131
+
132
+ setForegroundColor(color) {
133
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setForegroundColor');
134
+ if (nargs !== 1 || !is.string(color)) matchThrow();
135
+ const colorToRgb = (hex) => {
136
+ if (!hex || !hex.startsWith('#') || hex.length !== 7) return null;
137
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
138
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
139
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
140
+ return { red: r, green: g, blue: b };
141
+ };
142
+ return updateTextStyle(this, { foregroundColor: { color: { rgbColor: colorToRgb(color) } } }, 'foregroundColor');
143
+ }
144
+
145
+ getForegroundColor() {
146
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getForegroundColor');
147
+ if (nargs !== 0) matchThrow();
148
+ return this.getAttributes()[DocumentApp.Attribute.FOREGROUND_COLOR];
149
+ }
150
+
151
+ setBackgroundColor(color) {
152
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setBackgroundColor');
153
+ if (nargs !== 1 || !is.string(color)) matchThrow();
154
+ const colorToRgb = (hex) => {
155
+ if (!hex || !hex.startsWith('#') || hex.length !== 7) return null;
156
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
157
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
158
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
159
+ return { red: r, green: g, blue: b };
160
+ };
161
+ return updateTextStyle(this, { backgroundColor: { color: { rgbColor: colorToRgb(color) } } }, 'backgroundColor');
162
+ }
163
+
164
+ getBackgroundColor() {
165
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getBackgroundColor');
166
+ if (nargs !== 0) matchThrow();
167
+ return this.getAttributes()[DocumentApp.Attribute.BACKGROUND_COLOR];
168
+ }
169
+
170
+ setLinkUrl(url) {
171
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setLinkUrl');
172
+ if (nargs !== 1 || (!is.string(url) && !is.null(url))) matchThrow();
173
+ return updateTextStyle(this, { link: { url: url } }, 'link');
174
+ }
175
+
176
+ getLinkUrl() {
177
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.getLinkUrl');
178
+ if (nargs !== 0) matchThrow();
179
+ return this.getAttributes()[DocumentApp.Attribute.LINK_URL];
180
+ }
181
+
182
+ setStrikethrough(strikethrough) {
183
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.setStrikethrough');
184
+ if (nargs !== 1 || !is.boolean(strikethrough)) matchThrow();
185
+ return updateTextStyle(this, { strikethrough }, 'strikethrough');
186
+ }
187
+
188
+ isStrikethrough() {
189
+ const { nargs, matchThrow } = signatureArgs(arguments, 'Text.isStrikethrough');
190
+ if (nargs !== 0) matchThrow();
191
+ return !!this.getAttributes()[DocumentApp.Attribute.STRIKETHROUGH];
22
192
  }
23
193
 
24
194
  toString() {
@@ -28,4 +198,4 @@ class FakeText extends FakeElement {
28
198
 
29
199
  export const newFakeText = (...args) => Proxies.guard(new FakeText(...args));
30
200
 
31
- registerElement('TEXT', newFakeText);
201
+ registerElement('TEXT', newFakeText);