Skip to content

Commit b50d27b

Browse files
authored
feat(PR-40): forms.copy (#102)
New method to retrieve form, sanitize the payload and create a new one.
1 parent adccc13 commit b50d27b

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ Each one of them encapsulates the operations related to it (like listing, updati
177177

178178
- Deletes a typeform by UID
179179

180+
#### `forms.copy({ uid, workspaceUrl })`
181+
182+
- Copies an existing typeform with UID
183+
- `workspaceUrl` (optional) The URL of a workspace to copy the typeform into.
184+
180185
#### `forms.messages.get({ uid })`
181186

182187
- Get custom messages of the typeform with the given UID

src/forms.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Typeform } from './typeform-types'
22
import { autoPageItems } from './auto-page-items'
3+
import { removeFormKeys } from './utils'
34

45
export class Forms {
56
private _messages: FormMessages
@@ -86,6 +87,22 @@ export class Forms {
8687
data,
8788
})
8889
}
90+
91+
public async copy(args: {
92+
uid: string
93+
workspaceHref: string
94+
}): Promise<Typeform.Form> {
95+
const { uid, workspaceHref } = args
96+
const input = await this.get({ uid })
97+
98+
const data = removeFormKeys(input) as Typeform.Form
99+
100+
if (workspaceHref) {
101+
data.workspace = { href: workspaceHref }
102+
}
103+
104+
return this.create({ data })
105+
}
89106
}
90107

91108
class FormMessages {

src/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,27 @@ export const isMemberPropValid = (members: string | string[]): boolean => {
2121
// https://www.typeform.com/developers/get-started/#rate-limits
2222
export const rateLimit = () =>
2323
new Promise((resolve) => setTimeout(resolve, 500))
24+
25+
export const removeFormKeys = (
26+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
27+
input: any,
28+
removeKeys: string[] = ['id', 'integrations']
29+
): unknown => {
30+
if (Array.isArray(input)) {
31+
return input.map((item) => removeFormKeys(item))
32+
} else if (typeof input === 'object') {
33+
return Object.keys(input).reduce(
34+
(obj, key) => ({
35+
...obj,
36+
...(removeKeys.includes(key)
37+
? {}
38+
: key === 'application'
39+
? { application: removeFormKeys(input[key], ['installation_id']) }
40+
: { [key]: removeFormKeys(input[key]) }),
41+
}),
42+
{}
43+
)
44+
} else {
45+
return input
46+
}
47+
}

tests/unit/forms.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { axios } from '../common'
22
import { clientConstructor } from '../../src/create-client'
33
import { API_BASE_URL } from '../../src/constants'
44
import { Forms } from '../../src/forms'
5+
import * as utils from '../../src/utils'
56

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

122+
test('copy form retrieves form and creates form with correct payload', async () => {
123+
const uid = 'foo'
124+
const workspaceHref = 'http://localhost/workspace/bar'
125+
const formDefinition = {
126+
id: uid,
127+
title: 'hola',
128+
fields: [
129+
{ id: '1', title: 'foo', description: 'foobar' },
130+
{ id: '2', title: 'bar', supersized: true },
131+
],
132+
}
133+
const getSpy = jest
134+
.spyOn(formsRequest, 'get')
135+
.mockImplementationOnce(() => Promise.resolve(formDefinition))
136+
const removeFormKeysSpy = jest.spyOn(utils, 'removeFormKeys')
137+
const createSpy = jest.spyOn(formsRequest, 'create')
138+
await formsRequest.copy({
139+
uid,
140+
workspaceHref,
141+
})
142+
expect(getSpy).toHaveBeenCalledWith({ uid })
143+
expect(removeFormKeysSpy).toHaveBeenCalledWith(formDefinition)
144+
expect(createSpy).toHaveBeenCalledWith({
145+
data: {
146+
title: 'hola',
147+
fields: [
148+
{ title: 'foo', description: 'foobar' },
149+
{ title: 'bar', supersized: true },
150+
],
151+
workspace: { href: workspaceHref },
152+
},
153+
})
154+
})
155+
121156
test('get messages has the correct path and method ', async () => {
122157
await formsRequest.messages.get({ uid: 'abc123' })
123158

tests/unit/utils.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { isMemberPropValid, removeFormKeys } from '../../src/utils'
2+
3+
describe('#utils', () => {
4+
describe('#isMemberPropValid', () => {
5+
test.each(['1', 'foobar', [], ['foo', 'bar'], [1, 2, 3]])(
6+
'%p should return truthy',
7+
(value) => {
8+
expect(isMemberPropValid(value as string)).toBeTruthy()
9+
}
10+
)
11+
12+
test.each(['', 1, null, undefined, true, false, {}, { foo: 1 }])(
13+
'%p should return falsy',
14+
(value) => {
15+
expect(isMemberPropValid(value as string)).toBeFalsy()
16+
}
17+
)
18+
})
19+
20+
describe('#removeFormKeys', () => {
21+
it('should remove unwanted keys from the form object', () => {
22+
expect(
23+
removeFormKeys({
24+
id: 'foo',
25+
title: 'foobar',
26+
items: [
27+
{ id: 1, title: 'one' },
28+
{
29+
id: 2,
30+
title: 'two',
31+
application: { id: 'a', installation_id: 'b', value: 'c' },
32+
},
33+
{
34+
id: 3,
35+
title: 'three',
36+
values: ['foo', 'bar'],
37+
settings: { id: 33, value: false },
38+
integrations: [5, 6, 7],
39+
},
40+
],
41+
integrations: { foo: 'bar' },
42+
application: { id: 11, installation_id: 22 },
43+
settings: { id: 'bar', enabled: true },
44+
links: [
45+
'http://example.com',
46+
'http://localhost',
47+
{ id: 'foo', url: 'http://foo' },
48+
],
49+
})
50+
).toEqual({
51+
title: 'foobar',
52+
items: [
53+
{ title: 'one' },
54+
{ title: 'two', application: { id: 'a', value: 'c' } },
55+
{
56+
title: 'three',
57+
values: ['foo', 'bar'],
58+
settings: { value: false },
59+
},
60+
],
61+
application: { id: 11 },
62+
settings: { enabled: true },
63+
links: [
64+
'http://example.com',
65+
'http://localhost',
66+
{ url: 'http://foo' },
67+
],
68+
})
69+
})
70+
})
71+
})

0 commit comments

Comments
 (0)