Skip to content

Commit 2d26126

Browse files
authored
Merge pull request #1325 from sadiqkhoja/fixes/end-of-form-behavior
Fixes: ability to make multiple submission with public links
2 parents a0e0aac + bfea9f2 commit 2d26126

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

src/components/enketo-iframe.vue

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ const lastSubmitted = (enketoOnceId) => {
7070
7171
const enketoSrc = ref();
7272
73+
const single = computed(() => {
74+
const { query } = route;
75+
return (props.actionType === 'public-link' && query.single !== 'false') ||
76+
(props.actionType === 'new' && query.single === 'true');
77+
});
78+
7379
const setEnketoSrc = () => {
7480
let basePath = '/enketo-passthrough';
7581
// this is to avoid 404 warning
@@ -92,12 +98,14 @@ const setEnketoSrc = () => {
9298
if (props.actionType === 'offline') {
9399
return; // we don't render offline Enketo through central-frontend
94100
}
95-
if (props.actionType === 'public-link') {
101+
// for actionType 'new', we add '/single' only if 'single' query parameter is true
102+
// for actionType 'public-link', we add '/single' only if 'single' query parameter is not false
103+
if (single.value) {
96104
prefix += '/single';
97105
} else if (props.actionType === 'preview') {
98106
prefix += `/${props.actionType}`;
99107
}
100-
// for actionType 'new', we don't need to add anything to the prefix.
108+
101109
// we no longer render Enketo for Edit Submission from central-frontend.
102110
103111
if (props.enketoId === form.enketoOnceId) {
@@ -133,7 +141,7 @@ const handleIframeMessage = (event) => {
133141
try { eventData = JSON.parse(event.data); } catch {}
134142
135143
if (eventData?.enketoEvent === 'submissionsuccess') {
136-
if (props.actionType === 'public-link' && redirectUrl.value) {
144+
if (redirectUrl.value && single.value) {
137145
// for public link, we read return value from query parameter. The value could be 3rd party
138146
// site as well, typically a thank you page
139147
try {

test/components/enketo-iframe.spec.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { mockRouter } from '../util/router';
55
import { wait } from '../util/util';
66
import testData from '../data';
77
import { load } from '../util/http';
8+
import { mockLogin } from '../util/session';
89

910
const enketoId = 'sCTIfjC5LrUto4yVXRYJkNKzP7e53vo';
1011

@@ -49,6 +50,28 @@ describe('EnketoIframe', () => {
4950
});
5051
});
5152

53+
it('renders iframe with /single prefix when single=true query parameter for new submission', async () => {
54+
mockLogin();
55+
testData.extendedForms.createPast(1, { xmlFormId: 'a' });
56+
const app = await load('/projects/1/forms/a/submissions/new?single=true')
57+
.complete();
58+
59+
const iframe = app.find('iframe');
60+
iframe.attributes('src').should.contain('/enketo-passthrough/single/');
61+
});
62+
63+
it('renders iframe without /single prefix when single=false for public-link', async () => {
64+
testData.extendedForms.createPast(1, { xmlFormId: 'a' });
65+
66+
const app = await load(`/f/${enketoId}?single=false&st=token`)
67+
.restoreSession(false) // it's public link so not need to restore sessioin
68+
.complete();
69+
70+
const iframe = app.find('iframe');
71+
iframe.attributes('src').should.contain(`/enketo-passthrough/${enketoId}`);
72+
iframe.attributes('src').should.not.contain('/single/');
73+
});
74+
5275
it('redirects on submissionsuccess message with return_url - internal', async () => {
5376
const wrapper = mountComponent({
5477
props: { enketoId, actionType: 'public-link' },
@@ -86,6 +109,29 @@ describe('EnketoIframe', () => {
86109
fakeAssign.calledWith(new URL('http://example.com/projects/1')).should.be.true;
87110
});
88111

112+
it('redirect on submissionsuccess for new submission when single=true', async () => {
113+
const fakeAssign = sinon.fake();
114+
const wrapper = mountComponent({
115+
props: { enketoId, actionType: 'new' },
116+
container: {
117+
router: mockRouter('/?return_url=http://example.com/projects/1&single=true'),
118+
location: {
119+
origin: window.location.origin,
120+
assign: (url) => {
121+
fakeAssign(url);
122+
}
123+
}
124+
}
125+
});
126+
const iframe = wrapper.find('iframe');
127+
128+
const data = JSON.stringify({ enketoEvent: 'submissionsuccess' });
129+
await postMessageToParent(iframe, data);
130+
131+
fakeAssign.called.should.be.true;
132+
fakeAssign.args[0][0].href.should.equal('http://example.com/projects/1');
133+
});
134+
89135
it('does not redirect on invalid return URL', async () => {
90136
const fakeAssign = sinon.fake();
91137
const wrapper = mountComponent({
@@ -109,6 +155,51 @@ describe('EnketoIframe', () => {
109155
wrapper.vm.$router.push.called.should.be.false;
110156
});
111157

158+
it('does not redirect on submissionsuccess for public-link when single=false', async () => {
159+
const fakeAssign = sinon.fake();
160+
const wrapper = mountComponent({
161+
props: { enketoId, actionType: 'public-link' },
162+
container: {
163+
router: mockRouter('/?return_url=http://example.com/projects/1&single=false'),
164+
location: {
165+
origin: window.location.origin,
166+
assign: (url) => {
167+
fakeAssign(url);
168+
}
169+
}
170+
}
171+
});
172+
const iframe = wrapper.find('iframe');
173+
174+
const data = JSON.stringify({ enketoEvent: 'submissionsuccess' });
175+
await postMessageToParent(iframe, data);
176+
177+
fakeAssign.called.should.be.false;
178+
});
179+
180+
// 'single' query parameter is false implicitly
181+
it('does not redirects on submissionsuccess for new submission', async () => {
182+
const fakeAssign = sinon.fake();
183+
const wrapper = mountComponent({
184+
props: { enketoId, actionType: 'new' },
185+
container: {
186+
router: mockRouter('/?return_url=http://example.com/projects/1'),
187+
location: {
188+
origin: window.location.origin,
189+
assign: (url) => {
190+
fakeAssign(url);
191+
}
192+
}
193+
}
194+
});
195+
const iframe = wrapper.find('iframe');
196+
197+
const data = JSON.stringify({ enketoEvent: 'submissionsuccess' });
198+
await postMessageToParent(iframe, data);
199+
200+
fakeAssign.called.should.be.false;
201+
});
202+
112203
it('bubbles up the message event', async () => {
113204
const wrapper = mountComponent({
114205
props: { enketoId, actionType: 'new', instanceId: 'test-instance' },

0 commit comments

Comments
 (0)