Skip to content

Commit 984caa3

Browse files
authored
Merge pull request #151 from ttsukagoshi/test-translateRange
Add test for `translateRange()`
2 parents 12590d1 + 0538727 commit 984caa3

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

jest.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ module.exports = {
88
collectCoverage: true,
99
coverageDirectory: 'coverage',
1010
coverageProvider: 'v8',
11+
coverageThreshold: {
12+
global: {
13+
branches: 100,
14+
functions: 100,
15+
lines: 100,
16+
statements: 100,
17+
},
18+
},
1119
globals: {
1220
PropertiesService: {},
1321
SpreadsheetApp: {},

tests/translateRange.test.ts

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import { translateRange } from '../src/sheetsl';
2+
3+
describe('translateRange', () => {
4+
beforeEach(() => {
5+
global.UrlFetchApp = {
6+
fetch: jest.fn(() => ({
7+
getContentText: jest.fn(
8+
() => JSON.stringify({ translations: [{ text: 'Hallo, Welt!' }] }), // mock deepLTranslate
9+
),
10+
getResponseCode: jest.fn(() => 200), // mock handleDeepLErrors() to not throw errors
11+
})),
12+
} as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp;
13+
// eslint-disable-next-line @typescript-eslint/no-empty-function
14+
jest.spyOn(console, 'error').mockImplementation(() => {});
15+
});
16+
afterEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
it('should translate the selected range without errors', () => {
20+
const mockSelectedRangeValues = [
21+
['Hello, world!', 'Hello, world!'],
22+
['Hello, world!', ''], // empty cell
23+
['Hello, world!', 12345], // non-string cell
24+
];
25+
global.SpreadsheetApp = {
26+
getActiveSpreadsheet: jest.fn(() => ({
27+
getActiveSheet: jest.fn(() => ({
28+
getActiveRange: jest.fn(() => ({
29+
getValues: jest.fn(() => mockSelectedRangeValues),
30+
getRow: jest.fn(() => 1),
31+
getColumn: jest.fn(() => 1),
32+
getNumRows: jest.fn(() => mockSelectedRangeValues.length),
33+
getNumColumns: jest.fn(() => mockSelectedRangeValues[0].length),
34+
})),
35+
getRange: jest.fn(() => ({
36+
isBlank: jest.fn(() => true), // mock target range is blank
37+
setValues: jest.fn(),
38+
})),
39+
})),
40+
})),
41+
getUi: jest.fn(() => ({
42+
ButtonSet: {
43+
OK_CANCEL: 'ok_cancel',
44+
},
45+
alert: jest.fn(),
46+
})),
47+
} as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp;
48+
global.PropertiesService = {
49+
getUserProperties: jest.fn(() => ({
50+
getProperties: jest.fn(() => ({
51+
targetLocale: 'DE', // mock target locale set
52+
})),
53+
getProperty: jest.fn((key) => {
54+
if (key === 'deeplApiKey') return 'Sample-API-key'; // mock getDeepLApiKey()
55+
return null;
56+
}),
57+
})),
58+
} as unknown as GoogleAppsScript.Properties.PropertiesService;
59+
global.Utilities = {
60+
newBlob: jest.fn(() => ({
61+
getBytes: jest.fn(() => [0, 1, 2, 3]), // mock blob < THRESHOLD_BYTES (1900)
62+
})),
63+
sleep: jest.fn(),
64+
} as unknown as GoogleAppsScript.Utilities.Utilities;
65+
translateRange();
66+
expect(global.Utilities.sleep).toHaveBeenCalledTimes(5);
67+
expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(5);
68+
expect(console.error).not.toHaveBeenCalled();
69+
});
70+
describe('should catch errors', () => {
71+
it('when target locale is not set in user properties', () => {
72+
const mockSelectedRangeValues = [
73+
['Hello, world!', 'Hello, world!'],
74+
['Hello, world!', ''], // empty cell
75+
['Hello, world!', 12345], // non-string cell
76+
];
77+
global.SpreadsheetApp = {
78+
getActiveSpreadsheet: jest.fn(() => ({
79+
getActiveSheet: jest.fn(() => ({
80+
getActiveRange: jest.fn(() => ({
81+
getValues: jest.fn(() => mockSelectedRangeValues),
82+
getRow: jest.fn(() => 1),
83+
getColumn: jest.fn(() => 1),
84+
getNumRows: jest.fn(() => mockSelectedRangeValues.length),
85+
getNumColumns: jest.fn(() => mockSelectedRangeValues[0].length),
86+
})),
87+
})),
88+
})),
89+
getUi: jest.fn(() => ({
90+
alert: jest.fn(),
91+
})),
92+
} as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp;
93+
global.PropertiesService = {
94+
getUserProperties: jest.fn(() => ({
95+
getProperties: jest.fn(() => ({})), // mock target locale NOT set
96+
})),
97+
} as unknown as GoogleAppsScript.Properties.PropertiesService;
98+
translateRange();
99+
expect(global.Utilities.sleep).not.toHaveBeenCalled();
100+
expect(global.UrlFetchApp.fetch).not.toHaveBeenCalled();
101+
expect(console.error).toHaveBeenCalledWith(
102+
expect.stringMatching(
103+
/^Error: \[SheetsL\] Target Language Unavailable: Set the target language in Settings > Set Language of the add-on menu.\n/,
104+
),
105+
);
106+
});
107+
it('when cell(s) are not selected', () => {
108+
global.SpreadsheetApp = {
109+
getActiveSpreadsheet: jest.fn(() => ({
110+
getActiveSheet: jest.fn(() => ({
111+
getActiveRange: jest.fn(() => null), // mock no active range
112+
})),
113+
})),
114+
getUi: jest.fn(() => ({
115+
alert: jest.fn(),
116+
})),
117+
} as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp;
118+
global.PropertiesService = {
119+
getUserProperties: jest.fn(() => ({
120+
getProperties: jest.fn(() => ({
121+
targetLocale: 'DE', // mock target locale set
122+
})),
123+
})),
124+
} as unknown as GoogleAppsScript.Properties.PropertiesService;
125+
translateRange();
126+
expect(global.Utilities.sleep).not.toHaveBeenCalled();
127+
expect(global.UrlFetchApp.fetch).not.toHaveBeenCalled();
128+
expect(console.error).toHaveBeenCalledWith(
129+
expect.stringMatching(
130+
/^Error: \[SheetsL\] Select cells to translate.\n/,
131+
),
132+
);
133+
});
134+
it('when target range is not blank and the user cancels the process', () => {
135+
const mockSelectedRangeValues = [
136+
['Hello, world!', 'Hello, world!'],
137+
['Hello, world!', ''], // empty cell
138+
['Hello, world!', 12345], // non-string cell
139+
];
140+
global.SpreadsheetApp = {
141+
getActiveSpreadsheet: jest.fn(() => ({
142+
getActiveSheet: jest.fn(() => ({
143+
getActiveRange: jest.fn(() => ({
144+
getValues: jest.fn(() => mockSelectedRangeValues),
145+
getRow: jest.fn(() => 1),
146+
getColumn: jest.fn(() => 1),
147+
getNumRows: jest.fn(() => mockSelectedRangeValues.length),
148+
getNumColumns: jest.fn(() => mockSelectedRangeValues[0].length),
149+
})),
150+
getRange: jest.fn(() => ({
151+
isBlank: jest.fn(() => false), // mock target range is NOT blank
152+
setValues: jest.fn(),
153+
})),
154+
})),
155+
})),
156+
getUi: jest.fn(() => ({
157+
ButtonSet: {
158+
OK_CANCEL: 'ok_cancel',
159+
},
160+
Button: {
161+
OK: 'ok',
162+
},
163+
alert: jest.fn().mockReturnValueOnce('cancel'), // mock user cancel of the process
164+
})),
165+
} as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp;
166+
global.PropertiesService = {
167+
getUserProperties: jest.fn(() => ({
168+
getProperties: jest.fn(() => ({
169+
targetLocale: 'DE', // mock target locale set
170+
})),
171+
getProperty: jest.fn((key) => {
172+
if (key === 'deeplApiKey') return 'Sample-API-key:fx'; // mock getDeepLApiKey()
173+
return null;
174+
}),
175+
})),
176+
} as unknown as GoogleAppsScript.Properties.PropertiesService;
177+
global.Utilities = {
178+
newBlob: jest.fn(() => ({
179+
getBytes: jest.fn(() => [0, 1, 2, 3]), // mock blob < THRESHOLD_BYTES (1900)
180+
})),
181+
sleep: jest.fn(),
182+
} as unknown as GoogleAppsScript.Utilities.Utilities;
183+
translateRange();
184+
expect(global.Utilities.sleep).not.toHaveBeenCalled();
185+
expect(global.UrlFetchApp.fetch).not.toHaveBeenCalled();
186+
expect(console.error).toHaveBeenCalledWith(
187+
expect.stringMatching(/^Error: \[SheetsL\] Translation canceled.\n/),
188+
);
189+
});
190+
it('when a given text has a byte length that is larger than the threshold ', () => {
191+
const mockSelectedRangeValues = [
192+
['Hello, world!', 'Hello, world!'],
193+
['Hello, world!', ''], // empty cell
194+
['Hello, world!', 12345], // non-string cell
195+
];
196+
global.SpreadsheetApp = {
197+
getActiveSpreadsheet: jest.fn(() => ({
198+
getActiveSheet: jest.fn(() => ({
199+
getActiveRange: jest.fn(() => ({
200+
getValues: jest.fn(() => mockSelectedRangeValues),
201+
getRow: jest.fn(() => 1),
202+
getColumn: jest.fn(() => 1),
203+
getNumRows: jest.fn(() => mockSelectedRangeValues.length),
204+
getNumColumns: jest.fn(() => mockSelectedRangeValues[0].length),
205+
})),
206+
getRange: jest.fn(() => ({
207+
isBlank: jest.fn(() => true), // mock target range is blank
208+
setValues: jest.fn(),
209+
})),
210+
})),
211+
})),
212+
getUi: jest.fn(() => ({
213+
ButtonSet: {
214+
OK_CANCEL: 'ok_cancel',
215+
},
216+
alert: jest.fn(),
217+
})),
218+
} as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp;
219+
global.PropertiesService = {
220+
getUserProperties: jest.fn(() => ({
221+
getProperties: jest.fn(() => ({
222+
targetLocale: 'DE', // mock target locale set
223+
})),
224+
getProperty: jest.fn((key) => {
225+
if (key === 'deeplApiKey') return 'Sample-API-key:fx'; // mock getDeepLApiKey()
226+
return null;
227+
}),
228+
})),
229+
} as unknown as GoogleAppsScript.Properties.PropertiesService;
230+
global.Utilities = {
231+
newBlob: jest.fn(() => ({
232+
getBytes: jest.fn(() => new Array(2000) as number[]), // mock blob > THRESHOLD_BYTES (1900)
233+
})),
234+
sleep: jest.fn(),
235+
} as unknown as GoogleAppsScript.Utilities.Utilities;
236+
translateRange();
237+
expect(global.Utilities.sleep).not.toHaveBeenCalled();
238+
expect(global.UrlFetchApp.fetch).not.toHaveBeenCalled();
239+
expect(console.error).toHaveBeenCalledWith(
240+
expect.stringMatching(
241+
/^Error: \[SheetsL\] Cell content length exceeds Google's limits. Please consider splitting the content into multiple cells./,
242+
),
243+
);
244+
});
245+
});
246+
});

0 commit comments

Comments
 (0)