diff --git a/tests/deepLTranslate.test.ts b/tests/deepLTranslate.test.ts new file mode 100644 index 0000000..1d66ec7 --- /dev/null +++ b/tests/deepLTranslate.test.ts @@ -0,0 +1,86 @@ +import { deepLTranslate } from '../src/sheetsl'; + +describe('deepLTranslate', () => { + beforeEach(() => { + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperty: jest.fn(() => 'SampleApiKey:fx'), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.encodeURIComponent = jest.fn( + (text: string) => text, + ) as unknown as typeof encodeURIComponent; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + const mockSourceObjects = [ + { + note: 'with source language specified', + sourceLang: 'EN-US', + targetLang: 'DE', + sourceText: 'Hello, World!', + translatedText: 'Hallo, Welt!', + }, + { + note: 'without source language specified', + sourceLang: null, + targetLang: 'DE', + sourceText: 'Hello, World!', + translatedText: 'Hallo, Welt!', + }, + { + note: 'in an array of strings', + sourceLang: null, + targetLang: 'DE', + sourceText: ['Hello, World!', 'Hello, World!'], + translatedText: ['Hallo, Welt!', 'Hallo, Welt!'], + }, + ]; + it.each(mockSourceObjects)( + 'should translate text $note', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ({ note, sourceLang, targetLang, sourceText, translatedText }) => { + global.UrlFetchApp = { + fetch: jest.fn(() => ({ + getContentText: jest.fn(() => + JSON.stringify({ + translations: Array.isArray(translatedText) + ? translatedText.map((text) => ({ text: text })) + : [{ text: translatedText }], + }), + ), + getResponseCode: jest.fn(() => 200), + })), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + const translated = deepLTranslate(sourceText, sourceLang, targetLang); + expect(translated).toStrictEqual( + Array.isArray(translatedText) ? translatedText : [translatedText], + ); + expect(global.UrlFetchApp.fetch).toHaveBeenCalled(); + }, + ); + const mockSourceObjectsError = [ + { + note: 'when source text is null', + sourceLang: 'EN-US', + targetLang: 'DE', + sourceText: null, + }, + { + note: 'when source text is empty', + sourceLang: 'EN-US', + targetLang: 'DE', + sourceText: '', + }, + ]; + it.each(mockSourceObjectsError)( + 'should throw an error $note', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ({ note, sourceLang, targetLang, sourceText }) => { + expect(() => deepLTranslate(sourceText, sourceLang, targetLang)).toThrow( + new Error('[SheetsL] Empty input.'), + ); + }, + ); +}); diff --git a/tests/getDeepLApiBaseUrl.test.ts b/tests/getDeepLApiBaseUrl.test.ts index 43d7bb1..0010382 100644 --- a/tests/getDeepLApiBaseUrl.test.ts +++ b/tests/getDeepLApiBaseUrl.test.ts @@ -1,19 +1,15 @@ import { getDeepLApiBaseUrl } from '../src/sheetsl'; -const DEEPL_API_VERSION = 'v2'; -const DEEPL_API_BASE_URL_FREE = `https://api-free.deepl.com/${DEEPL_API_VERSION}/`; -const DEEPL_API_BASE_URL_PRO = `https://api.deepl.com/${DEEPL_API_VERSION}/`; - const patterns = [ { title: 'DeepL API Free account', input: 'xxxxxxxxxxx:fx', - expectedOutput: DEEPL_API_BASE_URL_FREE, + expectedOutput: 'https://api-free.deepl.com/v2/', }, { title: 'DeepL API Pro account', input: 'xxxxxxxxxxx', - expectedOutput: DEEPL_API_BASE_URL_PRO, + expectedOutput: 'https://api.deepl.com/v2/', }, ]; diff --git a/tests/setLanguage.test.ts b/tests/setLanguage.test.ts new file mode 100644 index 0000000..25155a7 --- /dev/null +++ b/tests/setLanguage.test.ts @@ -0,0 +1,320 @@ +import { setLanguage } from '../src/sheetsl'; + +describe('setLanguage', () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should set the language', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { OK: 'ok' }, + ButtonSet: { OK_CANCEL: 'ok_cancel' }, + prompt: jest + .fn() + .mockReturnValueOnce({ + // prompt for source language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'EN'), + }) + .mockReturnValueOnce({ + // prompt for target language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'DE'), + }) as unknown as GoogleAppsScript.Base.PromptResponse, + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({})), // No existing settings + getProperty: jest.fn(() => 'Sample-API-Key:fx'), + setProperties: jest.fn(), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest + .fn() + .mockReturnValueOnce({ + // deepLGetLanguages('source') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN', name: 'English' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }) + .mockReturnValueOnce({ + // deepLGetLanguages('target') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN-US', name: 'English (US)' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(2); + expect(console.error).not.toHaveBeenCalled(); + }); + describe('should catch an error', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('when the user cancels the language setup after notification of existing settings', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { YES: 'yes' }, + ButtonSet: { YES_NO: 'yes_no' }, + prompt: jest.fn(), + alert: jest.fn(() => 'no'), // cancel the setup + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({ + targetLocale: 'DE', // existing settings + })), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest.fn(), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).not.toHaveBeenCalled(); + expect(console.error).toHaveBeenCalledWith( + expect.stringMatching( + /^Error: \[SheetsL\] Canceled: Language setting has been canceled.\n/, + ), + ); + }); + it('when the user cancels the language setup after prompted to enter source locale', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { OK: 'ok' }, + ButtonSet: { OK_CANCEL: 'ok_cancel' }, + prompt: jest.fn().mockReturnValueOnce({ + // prompt for source language + getSelectedButton: jest.fn(() => 'cancel'), // cancel the setup after prompted to enter source locale + getResponseText: jest.fn(() => 'EN'), + }) as unknown as GoogleAppsScript.Base.PromptResponse, + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({})), // No existing settings + getProperty: jest.fn(() => 'Sample-API-Key:fx'), + setProperties: jest.fn(), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest + .fn() + .mockReturnValueOnce({ + // deepLGetLanguages('source') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN', name: 'English' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }) + .mockReturnValueOnce({ + // deepLGetLanguages('target') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN-US', name: 'English (US)' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledWith( + expect.stringMatching( + /^Error: \[SheetsL\] Canceled: Language setting has been canceled.\n/, + ), + ); + }); + it('when the user enters a value not included in the list of available source languages', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { OK: 'ok' }, + ButtonSet: { OK_CANCEL: 'ok_cancel' }, + prompt: jest.fn().mockReturnValueOnce({ + // prompt for source language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'JA'), // a source locale that is not included in the response value of deepLGetLanguages('source') + }) as unknown as GoogleAppsScript.Base.PromptResponse, + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({})), // No existing settings + getProperty: jest.fn(() => 'Sample-API-Key:fx'), + setProperties: jest.fn(), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest + .fn() + .mockReturnValueOnce({ + // deepLGetLanguages('source') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN', name: 'English' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }) + .mockReturnValueOnce({ + // deepLGetLanguages('target') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN-US', name: 'English (US)' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledWith( + expect.stringMatching( + /^Error: \[SheetsL\] Invalid Value \(JA\): Enter a valid value.\n/, + ), + ); + }); + it('when the user cancels the language setup after prompted to enter target locale', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { OK: 'ok' }, + ButtonSet: { OK_CANCEL: 'ok_cancel' }, + prompt: jest + .fn() + .mockReturnValueOnce({ + // prompt for source language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'EN'), + }) + .mockReturnValueOnce({ + // prompt for target language + getSelectedButton: jest.fn(() => 'cancel'), // cancel the setup after prompted to enter target locale + getResponseText: jest.fn(() => 'DE'), + }) as unknown as GoogleAppsScript.Base.PromptResponse, + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({})), // No existing settings + getProperty: jest.fn(() => 'Sample-API-Key:fx'), + setProperties: jest.fn(), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest + .fn() + .mockReturnValueOnce({ + // deepLGetLanguages('source') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN', name: 'English' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }) + .mockReturnValueOnce({ + // deepLGetLanguages('target') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN-US', name: 'English (US)' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledWith( + expect.stringMatching( + /^Error: \[SheetsL\] Canceled: Language setting has been canceled.\n/, + ), + ); + }); + it('when the user enters a value not included in the list of available source languages', () => { + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + Button: { OK: 'ok' }, + ButtonSet: { OK_CANCEL: 'ok_cancel' }, + prompt: jest + .fn() + .mockReturnValueOnce({ + // prompt for source language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'DE'), + }) + .mockReturnValueOnce({ + // prompt for target language + getSelectedButton: jest.fn(() => 'ok'), + getResponseText: jest.fn(() => 'JA'), // a target locale that is not included in the response value of deepLGetLanguages('target') + }) as unknown as GoogleAppsScript.Base.PromptResponse, + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperties: jest.fn(() => ({})), // No existing settings + getProperty: jest.fn(() => 'Sample-API-Key:fx'), + setProperties: jest.fn(), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.UrlFetchApp = { + fetch: jest + .fn() + .mockReturnValueOnce({ + // deepLGetLanguages('source') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN', name: 'English' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }) + .mockReturnValueOnce({ + // deepLGetLanguages('target') + getContentText: jest.fn(() => + JSON.stringify([ + { language: 'EN-US', name: 'English (US)' }, + { language: 'DE', name: 'German' }, + ]), + ), + getResponseCode: jest.fn(() => 200), + }), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + setLanguage(); + expect(global.UrlFetchApp.fetch).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledWith( + expect.stringMatching( + /^Error: \[SheetsL\] Invalid Value \(JA\): Enter a valid value.\n/, + ), + ); + }); + }); +});