Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing docs to readme #4

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

- Removed the `__sdk.js` developer file from the respoitory.
- Fixed the types for runAirtableScript.

## [0.0.2] - 2025-01-09

Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,47 @@ const result = await runAirtableScript({

You can pass one of `us`, `friendly`, `european`, or `iso`.

### Mocking fetch requests

Airtable scripts can either use `fetch`, or in extensions `remoteFetchAsync` to make HTTP requests. You can mock these requests using the `fetchMock` setting:

```js
const result = await runAirtableScript({
script: myScript,
base: baseFixture,
fetchMock: (url, request) => {
return {
status: 200,
body: JSON.stringify({ message: 'Hello, world!' }),
}
},
})
```

### Mocking user inputs

You can mock any `input` from either an automation input or user interaction using the `mockInput` setting:

```js
const results = await runAirtableScript({
script: `
const text = await input.textAsync('Select a table')
output.inspect(text)
`,
base: randomRecords,
mockInput: {
// @ts-ignore
textAsync: (label) => {
if (label === 'Select a table') {
return 'text123'
}
},
},
})
```

Every [input method for extensions or automations](https://airtable.com/developers/scripting/api/input) are available to be mocked. Check out the [input.test.ts](./test/input.test.ts) file for examples.

### Results

The results from calling `runAirtableScript` are an object with several properties:
Expand Down
3 changes: 3 additions & 0 deletions src/environment/console-aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ type ConsoleAggregator = {
/**
* Returns a console object that aggregates all messages logged to it.
* Used to override the global console object in tests.
*
* The _getMessages method is called after a test is run to pass the
* messages to the test runner.
*/
const consoleAggregator = (): ConsoleAggregator => {
const consoleMessages: ConsoleMessage[] = []
Expand Down
6 changes: 2 additions & 4 deletions src/environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import { OUTPUT_CLEAR } from './output-clear'

type StrictGlobal = {
runAirtableScript: (options: RunScriptOptions) => Promise<RunScriptResult>
MutationTypes: typeof MutationTypes | undefined
OUTPUT_CLEAR: typeof OUTPUT_CLEAR | undefined
MutationTypes: typeof MutationTypes
OUTPUT_CLEAR: typeof OUTPUT_CLEAR
}

export type AirtableScriptGlobal = Required<StrictGlobal>

export class AirtableScriptEnvironment extends NodeEnvironment {
declare global: StrictGlobal & NodeEnvironment['global']

constructor(config: JestEnvironmentConfig, _context: EnvironmentContext) {
super(config, _context)
this.global.runAirtableScript = runAirtableScript
Expand Down
11 changes: 8 additions & 3 deletions src/environment/run-airtable-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {

type DefaultDateLocale = 'friendly' | 'us' | 'european' | 'iso'

type Output = [string, number, boolean] | { key: string; value: string }[]

type RunScriptOptions = {
script: string
base: { base: unknown } | unknown
Expand All @@ -24,7 +26,7 @@ type RunScriptOptions = {
}

type RunScriptResult = {
output: unknown[]
output: Output
mutations: Mutation[]
console: ConsoleMessage[]
}
Expand All @@ -47,6 +49,9 @@ type RunContext = {

let sdkScript: string | null = null

/**
* Runs a given Airtable script against a base fixture. Full definition is in src/environment/index.ts
*/
const runAirtableScript = async ({
script,
base,
Expand Down Expand Up @@ -77,8 +82,8 @@ const runAirtableScript = async ({
__defaultDateLocale: defaultDateLocale,
console: consoleAggregator(),
}
vm.createContext(context)

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.
Expand All @@ -90,7 +95,7 @@ const runAirtableScript = async ({
)

return {
output: context.__output || [],
output: (context.__output as Output) || [],
mutations: context.__mutations || [],
console: context.console._getMessages(),
}
Expand Down
7 changes: 7 additions & 0 deletions src/environment/sdk/globals/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ type ExtensionOutput = {
// @ts-ignore
globalThis.__output = []

/**
* The output object if a script is being used within an automation.
*/
const automationOutput: AutomationOutput = {
set: (key, value) => {
__output.push({ key, value })
},
}

/**
* The output object if a script is being used within an extension.
*/
const extensionOutput: ExtensionOutput = {
/**
* Displays the given text on-screen.
Expand Down Expand Up @@ -88,6 +94,7 @@ const extensionOutput: ExtensionOutput = {
},
}

// Use one of the two outputs based on the context (extension or automation)
const output: AutomationOutput | ExtensionOutput = globalThis.__inAutomation
? automationOutput
: extensionOutput
Expand Down
3 changes: 2 additions & 1 deletion src/environment/sdk/lib/pascal-case.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* A pascal case utility function.
* A pascal case utility function. Used for schema IDs, which always start with
* a three-letter lower-case prefix like tblTableId or fldFieldId.
*/
const pascalCase = (str: string): string =>
str
Expand Down
30 changes: 29 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
export { AirtableScriptEnvironment as default } from './environment'

import type { AirtableScriptGlobal } from './environment'
import type {
RunScriptOptions,
RunScriptResult,
} from './environment/run-airtable-script'

declare global {
const runAirtableScript: AirtableScriptGlobal['runAirtableScript']
/**
* Runs a given Airtable script against a base fixture. Returns the output, console, and base
* mutations that the script generated.
*
* @param {RunScriptOptions} options
* @param {string} options.script The script to run, as a string.
* @param {Base} options.base The base fixture to run the script against. Generate this using
* the Test Fixture Generator extension.
* @param {boolean} [options.inAutomation=false] Whether the script is running in an automation. Defaults to false.
* @param {DefaultCursor | false} [options.defaultCursor=false] The default cursor to use for the script. Defaults to false.
* @param {Collaborator | false} [options.currentUser=false] The current user to use for the script. Defaults to false.
* @param {unknown} [options.mockInput=undefined] The mock input for the script. See the README for more information.
* @param {Function | false} [options.mockFetch=false] A function that mocks any fetch requests.
* @param {DefaultDateLocale} [options.defaultDateLocale='us'] The date format to use when a date field uses the "local" date format. Defaults to 'us'.
* @returns {Promise<RunScriptResult>}
*/
const runAirtableScript: (
options: RunScriptOptions
) => Promise<RunScriptResult>
/**
* An object containing the different types of mutations that can be tracked in a script.
*/
const MutationTypes: AirtableScriptGlobal['MutationTypes']
/**
* A special string that is used to denote that a call to output.clear() was made in the script.
*/
const OUTPUT_CLEAR: AirtableScriptGlobal['OUTPUT_CLEAR']
}
10 changes: 6 additions & 4 deletions test/input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ describe('Input', () => {
config: () => ({ [key]: 'tbl123' }),
},
})
expect(results.output[0].key).toEqual('config')
expect(results.output[0].value).toEqual(
JSON.stringify({ [key]: 'tbl123' })
)
if (typeof results.output[0] === 'object') {
expect(results.output[0].key).toEqual('config')
expect(results.output[0].value).toEqual(
JSON.stringify({ [key]: 'tbl123' })
)
}
})
})
describe('extension script', () => {
Expand Down
Loading