Skip to content

Commit

Permalink
feat(PR-40): forms.copy (#102)
Browse files Browse the repository at this point in the history
New method to retrieve form, sanitize the payload and create a new one.
  • Loading branch information
mathio authored Oct 12, 2023
1 parent adccc13 commit b50d27b
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ Each one of them encapsulates the operations related to it (like listing, updati

- Deletes a typeform by UID

#### `forms.copy({ uid, workspaceUrl })`

- Copies an existing typeform with UID
- `workspaceUrl` (optional) The URL of a workspace to copy the typeform into.

#### `forms.messages.get({ uid })`

- Get custom messages of the typeform with the given UID
Expand Down
17 changes: 17 additions & 0 deletions src/forms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Typeform } from './typeform-types'
import { autoPageItems } from './auto-page-items'
import { removeFormKeys } from './utils'

export class Forms {
private _messages: FormMessages
Expand Down Expand Up @@ -86,6 +87,22 @@ export class Forms {
data,
})
}

public async copy(args: {
uid: string
workspaceHref: string
}): Promise<Typeform.Form> {
const { uid, workspaceHref } = args
const input = await this.get({ uid })

const data = removeFormKeys(input) as Typeform.Form

if (workspaceHref) {
data.workspace = { href: workspaceHref }
}

return this.create({ data })
}
}

class FormMessages {
Expand Down
24 changes: 24 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ export const isMemberPropValid = (members: string | string[]): boolean => {
// https://www.typeform.com/developers/get-started/#rate-limits
export const rateLimit = () =>
new Promise((resolve) => setTimeout(resolve, 500))

export const removeFormKeys = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: any,
removeKeys: string[] = ['id', 'integrations']
): unknown => {
if (Array.isArray(input)) {
return input.map((item) => removeFormKeys(item))
} else if (typeof input === 'object') {
return Object.keys(input).reduce(
(obj, key) => ({
...obj,
...(removeKeys.includes(key)
? {}
: key === 'application'
? { application: removeFormKeys(input[key], ['installation_id']) }
: { [key]: removeFormKeys(input[key]) }),
}),
{}
)
} else {
return input
}
}
35 changes: 35 additions & 0 deletions tests/unit/forms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { axios } from '../common'
import { clientConstructor } from '../../src/create-client'
import { API_BASE_URL } from '../../src/constants'
import { Forms } from '../../src/forms'
import * as utils from '../../src/utils'

beforeEach(() => {
axios.reset()
Expand Down Expand Up @@ -118,6 +119,40 @@ test('create form has the correct path and method ', async () => {
expect(axios.history.post[0].url).toBe(`${API_BASE_URL}/forms`)
})

test('copy form retrieves form and creates form with correct payload', async () => {
const uid = 'foo'
const workspaceHref = 'http://localhost/workspace/bar'
const formDefinition = {
id: uid,
title: 'hola',
fields: [
{ id: '1', title: 'foo', description: 'foobar' },
{ id: '2', title: 'bar', supersized: true },
],
}
const getSpy = jest
.spyOn(formsRequest, 'get')
.mockImplementationOnce(() => Promise.resolve(formDefinition))
const removeFormKeysSpy = jest.spyOn(utils, 'removeFormKeys')
const createSpy = jest.spyOn(formsRequest, 'create')
await formsRequest.copy({
uid,
workspaceHref,
})
expect(getSpy).toHaveBeenCalledWith({ uid })
expect(removeFormKeysSpy).toHaveBeenCalledWith(formDefinition)
expect(createSpy).toHaveBeenCalledWith({
data: {
title: 'hola',
fields: [
{ title: 'foo', description: 'foobar' },
{ title: 'bar', supersized: true },
],
workspace: { href: workspaceHref },
},
})
})

test('get messages has the correct path and method ', async () => {
await formsRequest.messages.get({ uid: 'abc123' })

Expand Down
71 changes: 71 additions & 0 deletions tests/unit/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { isMemberPropValid, removeFormKeys } from '../../src/utils'

describe('#utils', () => {
describe('#isMemberPropValid', () => {
test.each(['1', 'foobar', [], ['foo', 'bar'], [1, 2, 3]])(
'%p should return truthy',
(value) => {
expect(isMemberPropValid(value as string)).toBeTruthy()
}
)

test.each(['', 1, null, undefined, true, false, {}, { foo: 1 }])(
'%p should return falsy',
(value) => {
expect(isMemberPropValid(value as string)).toBeFalsy()
}
)
})

describe('#removeFormKeys', () => {
it('should remove unwanted keys from the form object', () => {
expect(
removeFormKeys({
id: 'foo',
title: 'foobar',
items: [
{ id: 1, title: 'one' },
{
id: 2,
title: 'two',
application: { id: 'a', installation_id: 'b', value: 'c' },
},
{
id: 3,
title: 'three',
values: ['foo', 'bar'],
settings: { id: 33, value: false },
integrations: [5, 6, 7],
},
],
integrations: { foo: 'bar' },
application: { id: 11, installation_id: 22 },
settings: { id: 'bar', enabled: true },
links: [
'http://example.com',
'http://localhost',
{ id: 'foo', url: 'http://foo' },
],
})
).toEqual({
title: 'foobar',
items: [
{ title: 'one' },
{ title: 'two', application: { id: 'a', value: 'c' } },
{
title: 'three',
values: ['foo', 'bar'],
settings: { value: false },
},
],
application: { id: 11 },
settings: { enabled: true },
links: [
'http://example.com',
'http://localhost',
{ url: 'http://foo' },
],
})
})
})
})

0 comments on commit b50d27b

Please sign in to comment.