From 0fc7a3f26778560243e4ef58f406faf0232bfa50 Mon Sep 17 00:00:00 2001 From: Kevin Miller Date: Wed, 12 Mar 2025 11:18:03 -0700 Subject: [PATCH] feat: add support for thrown errors --- CHANGELOG.md | 6 +++ README.md | 16 ++++++++ package.json | 2 +- src/environment/run-airtable-script.ts | 32 ++++++++++++---- test/catch-errors.test.ts | 52 ++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 test/catch-errors.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f49f5a..07553c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.0] - 2025-03-12 + +### Added + +- Handled thrown errors within script code. Errors are now caught and returned in the results object. + ## [0.0.4] - 2025-01-13 ### Changed diff --git a/README.md b/README.md index 0ea1ec8..436d6f8 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,22 @@ it('logs a message', async () => { }) ``` +#### Thrown errors + +If your script throws a new error to stop execution, you can catch that error in the results object. The error is stored in the `thrownErrors` property of the results object: + +```js +it('throws an error', async () => { + const { thrownError } = await runAirtableScript({ + script: ` + throw new Error('This is an error') + `, + base: testBase, + }) + expect(thrownError.message).toEqual('This is an error') +}) +``` + ## Developing locally The environment variable `JEST_AIRTABLE_TS_DEV` should be set to `true` so that the `runScript` function pulls the compiled SDK mock from the `./src/environment/sdk/__sdk.js` file. This is already set to `true` in the `package.json` file. diff --git a/package.json b/package.json index c5bd603..e355a87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jest-environment-airtable-script", - "version": "0.0.4", + "version": "0.1.0", "description": "A jest environment for testing Airtable scripts in extensions and automations", "license": "Apache-2.0", "author": "", diff --git a/src/environment/run-airtable-script.ts b/src/environment/run-airtable-script.ts index a1bcc20..14fbd11 100644 --- a/src/environment/run-airtable-script.ts +++ b/src/environment/run-airtable-script.ts @@ -29,6 +29,7 @@ type RunScriptResult = { output: Output mutations: Mutation[] console: ConsoleMessage[] + thrownError: false | unknown } type RunContext = { @@ -43,6 +44,7 @@ type RunContext = { // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type __mockFetch?: Function | false __input?: unknown + __scriptError: false | unknown __defaultDateLocale: DefaultDateLocale console: ConsoleAggregator } @@ -80,24 +82,38 @@ const runAirtableScript = async ({ __mockInput: mockInput, __mockFetch: mockFetch, __defaultDateLocale: defaultDateLocale, + __scriptError: false, console: consoleAggregator(), } vm.createContext(context) vm.runInContext(sdkScript, context) - // We need to run the script in an async function so that we can use await - // directly inside the script. - vm.runInContext( - `;(async () => { - ${script} - })()`, - context - ) + + let thrownError: false | unknown = false + + try { + // We need to run the script in an async function so that we can use await + // directly inside the script. + await vm.runInContext( + `;(async () => { + try { + ${script} + } catch(e) { + this.__scriptError = e; + } + })()`, + context + ) + thrownError = context.__scriptError || false + } catch (error) { + thrownError = error + } return { output: (context.__output as Output) || [], mutations: context.__mutations || [], console: context.console._getMessages(), + thrownError, } } export default runAirtableScript diff --git a/test/catch-errors.test.ts b/test/catch-errors.test.ts new file mode 100644 index 0000000..26257d6 --- /dev/null +++ b/test/catch-errors.test.ts @@ -0,0 +1,52 @@ +describe('Catch errors test', () => { + it('sets thrownError to false if none are thrown', async () => { + const randomTable = Math.random().toString(36).substring(7) + const results = await runAirtableScript({ + script: ` + const table = base.getTable('Table ${randomTable}') + output.text(table.id) + `, + base: { + base: { + tables: [ + { + id: 'tbl1', + name: 'Table 1', + }, + { + id: `tbl${randomTable}`, + name: `Table ${randomTable}`, + }, + ], + }, + }, + }) + expect(results.thrownError).toBe(false) + }) + + it('catches errors thrown in the script', async () => { + const randomTable = Math.random().toString(36).substring(7) + const results = await runAirtableScript({ + script: ` + const table = base.getTable('Table ${randomTable}') + throw new Error('This is an error') + `, + base: { + base: { + tables: [ + { + id: 'tbl1', + name: 'Table 1', + }, + { + id: `tbl${randomTable}`, + name: `Table ${randomTable}`, + }, + ], + }, + }, + }) + + expect(results.thrownError.message).toBe('This is an error') + }) +})