diff --git a/app/server/lib/AppEndpoint.ts b/app/server/lib/AppEndpoint.ts index 89e0f207..c778fb30 100644 --- a/app/server/lib/AppEndpoint.ts +++ b/app/server/lib/AppEndpoint.ts @@ -25,10 +25,11 @@ import {addOrgToPathIfNeeded, pruneAPIResult, trustOrigin} from 'app/server/lib/ import {ISendAppPageOptions} from 'app/server/lib/sendAppPage'; export interface AttachOptions { - app: express.Application; // Express app to which to add endpoints - middleware: express.RequestHandler[]; // Middleware to apply for all endpoints except docs - docMiddleware: express.RequestHandler[]; // Middleware to apply for doc landing pages - forceLogin: express.RequestHandler|null; // Method to force user to login (if logins are possible) + app: express.Application; // Express app to which to add endpoints + middleware: express.RequestHandler[]; // Middleware to apply for all endpoints except docs and forms + docMiddleware: express.RequestHandler[]; // Middleware to apply for doc landing pages + formMiddleware: express.RequestHandler[]; // Middleware to apply for form landing pages + forceLogin: express.RequestHandler|null; // Method to force user to login (if logins are possible) docWorkerMap: IDocWorkerMap|null; sendAppPage: (req: express.Request, resp: express.Response, options: ISendAppPageOptions) => Promise; dbManager: HomeDBManager; @@ -37,8 +38,8 @@ export interface AttachOptions { } export function attachAppEndpoint(options: AttachOptions): void { - const {app, middleware, docMiddleware, docWorkerMap, forceLogin, - sendAppPage, dbManager, plugins, gristServer} = options; + const {app, middleware, docMiddleware, formMiddleware, docWorkerMap, + forceLogin, sendAppPage, dbManager, plugins, gristServer} = options; // Per-workspace URLs open the same old Home page, and it's up to the client to notice and // render the right workspace. app.get(['/', '/ws/:wsId', '/p/:page'], ...middleware, expressWrap(async (req, res) => @@ -226,7 +227,7 @@ export function attachAppEndpoint(options: AttachOptions): void { ...docMiddleware, docHandler); app.get('/:urlId([^-/]{12,})(/:slug([^/]+):remainder(*))?', ...docMiddleware, docHandler); - app.get('/forms/:urlId([^/]+)/:sectionId', ...middleware, expressWrap(async (req, res) => { + app.get('/forms/:urlId([^/]+)/:sectionId', ...formMiddleware, expressWrap(async (req, res) => { const formUrl = gristServer.getHomeUrl(req, `/api/s/${req.params.urlId}/forms/${req.params.sectionId}`); const response = await fetch(formUrl, { diff --git a/app/server/lib/FlexServer.ts b/app/server/lib/FlexServer.ts index c4e2ebe7..5d95661c 100644 --- a/app/server/lib/FlexServer.ts +++ b/app/server/lib/FlexServer.ts @@ -1036,6 +1036,9 @@ export class FlexServer implements GristServer { this._redirectToOrgMiddleware, welcomeNewUser ], + formMiddleware: [ + forcedLoginMiddleware, + ], forceLogin: this._redirectToLoginUnconditionally, docWorkerMap: isSingleUserMode() ? null : this._docWorkerMap, sendAppPage: this._sendAppPage, diff --git a/test/nbrowser/FormView.ts b/test/nbrowser/FormView.ts index 7dd8f364..5a59a08f 100644 --- a/test/nbrowser/FormView.ts +++ b/test/nbrowser/FormView.ts @@ -21,17 +21,6 @@ describe('FormView', function() { afterEach(() => gu.checkForErrors()); - before(async function() { - const session = await gu.session().login(); - docId = await session.tempNewDoc(cleanup); - api = session.createHomeApi(); - await driver.executeScript(createClipboardTextArea); - }); - - after(async function() { - await driver.executeScript(removeClipboardTextArea); - }); - /** * Adds a temporary textarea to the document for pasting the contents of * the clipboard. @@ -123,1070 +112,1115 @@ describe('FormView', function() { assert.deepEqual(await api.getTable(docId, 'Table1').then(t => t.D), values); } - it('updates creator panel when navigated away', async function() { - // Add 2 new pages. - await gu.addNewPage('Form', 'New Table', {tableName: 'TabA'}); - await gu.renamePage('TabA'); - await gu.addNewPage('Form', 'New Table', {tableName: 'TabB'}); + describe('on personal site', async function() { + before(async function() { + const session = await gu.session().login(); + docId = await session.tempNewDoc(cleanup); + api = session.createHomeApi(); + await driver.executeScript(createClipboardTextArea); + }); - // Open the creator panel on field tab - await gu.openColumnPanel(); + after(async function() { + await driver.executeScript(removeClipboardTextArea); + }); - // Select A column - await question('A').click(); + it('updates creator panel when navigated away', async function() { + // Add 2 new pages. + await gu.addNewPage('Form', 'New Table', {tableName: 'TabA'}); + await gu.renamePage('TabA'); + await gu.addNewPage('Form', 'New Table', {tableName: 'TabB'}); - // Make sure it is selected. - assert.equal(await selectedLabel(), 'A'); + // Open the creator panel on field tab + await gu.openColumnPanel(); - // And creator panel reflects it. - assert.equal(await driver.find('.test-field-label').value(), "A"); + // Select A column + await question('A').click(); - // Now switch to page TabA. - await gu.openPage('TabA'); + // Make sure it is selected. + assert.equal(await selectedLabel(), 'A'); - // And select B column. - await question('B').click(); - assert.equal(await selectedLabel(), 'B'); + // And creator panel reflects it. + assert.equal(await driver.find('.test-field-label').value(), "A"); - // Make sure creator panel reflects it (it didn't). - assert.equal(await driver.find('.test-field-label').value(), "B"); + // Now switch to page TabA. + await gu.openPage('TabA'); - await gu.undo(2); // There was a bug with second undo. - await gu.undo(); - }); + // And select B column. + await question('B').click(); + assert.equal(await selectedLabel(), 'B'); - it('triggers trigger formulas', async function() { - const formUrl = await createFormWith('Text'); - // Add a trigger formula for this column. - await gu.showRawData(); - await gu.getCell('D', 1).click(); - await gu.openColumnPanel(); - await driver.find(".test-field-set-trigger").click(); - await gu.waitAppFocus(false); - await gu.sendKeys('"Hello from trigger"', Key.ENTER); - await gu.waitForServer(); - await gu.closeRawTable(); - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); - }); - await expectSingle('Hello from trigger'); - await removeForm(); - }); + // Make sure creator panel reflects it (it didn't). + assert.equal(await driver.find('.test-field-label').value(), "B"); - it('can submit a form with Text field', async function() { - const formUrl = await createFormWith('Text'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).click(); - await gu.sendKeys('Hello World'); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + await gu.undo(2); // There was a bug with second undo. + await gu.undo(); }); - // Make sure we see the new record. - await expectSingle('Hello World'); - await removeForm(); - }); - it('can submit a form with Numeric field', async function() { - const formUrl = await createFormWith('Numeric'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).click(); - await gu.sendKeys('1984'); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('triggers trigger formulas', async function() { + const formUrl = await createFormWith('Text'); + // Add a trigger formula for this column. + await gu.showRawData(); + await gu.getCell('D', 1).click(); + await gu.openColumnPanel(); + await driver.find(".test-field-set-trigger").click(); + await gu.waitAppFocus(false); + await gu.sendKeys('"Hello from trigger"', Key.ENTER); + await gu.waitForServer(); + await gu.closeRawTable(); + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectSingle('Hello from trigger'); + await removeForm(); }); - // Make sure we see the new record. - await expectSingle(1984); - await removeForm(); - }); - it('can submit a form with Date field', async function() { - const formUrl = await createFormWith('Date'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).click(); - await driver.executeScript( - () => (document.querySelector('input[name="D"]') as HTMLInputElement).value = '2000-01-01' - ); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with Text field', async function() { + const formUrl = await createFormWith('Text'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await gu.sendKeys('Hello World'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + // Make sure we see the new record. + await expectSingle('Hello World'); + await removeForm(); }); - // Make sure we see the new record. - await expectSingle(/* 2000-01-01 */946684800); - await removeForm(); - }); - - it('can submit a form with Choice field', async function() { - const formUrl = await createFormWith('Choice'); - // Add some options. - await gu.openColumnPanel(); - await gu.choicesEditor.edit(); - await gu.choicesEditor.add('Foo'); - await gu.choicesEditor.add('Bar'); - await gu.choicesEditor.add('Baz'); - await gu.choicesEditor.save(); - await gu.toggleSidePanel('right', 'close'); - - // We need to press preview, as form is not saved yet. - await gu.scrollActiveViewTop(); - await gu.waitToPass(async () => { - assert.isTrue(await driver.find('.test-forms-preview').isDisplayed()); + it('can submit a form with Numeric field', async function() { + const formUrl = await createFormWith('Numeric'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await gu.sendKeys('1984'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + // Make sure we see the new record. + await expectSingle(1984); + await removeForm(); }); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - // Make sure options are there. - assert.deepEqual( - await driver.findAll('select[name="D"] option', e => e.getText()), [CHOOSE_TEXT, 'Foo', 'Bar', 'Baz'] - ); - await driver.findWait('select[name="D"]', 1000).click(); - await driver.find("option[value='Bar']").click(); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); - }); - await expectSingle('Bar'); - await removeForm(); - }); - it('can submit a form with Integer field', async function() { - const formUrl = await createFormWith('Integer', true); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).click(); - await gu.sendKeys('1984'); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with Date field', async function() { + const formUrl = await createFormWith('Date'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await driver.executeScript( + () => (document.querySelector('input[name="D"]') as HTMLInputElement).value = '2000-01-01' + ); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + // Make sure we see the new record. + await expectSingle(/* 2000-01-01 */946684800); + await removeForm(); }); - // Make sure we see the new record. - await expectSingle(1984); - await removeForm(); - }); - it('can submit a form with Toggle field', async function() { - const formUrl = await createFormWith('Toggle', true); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).findClosest("label").click(); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with Choice field', async function() { + const formUrl = await createFormWith('Choice'); + // Add some options. + await gu.openColumnPanel(); + + await gu.choicesEditor.edit(); + await gu.choicesEditor.add('Foo'); + await gu.choicesEditor.add('Bar'); + await gu.choicesEditor.add('Baz'); + await gu.choicesEditor.save(); + await gu.toggleSidePanel('right', 'close'); + + // We need to press preview, as form is not saved yet. + await gu.scrollActiveViewTop(); + await gu.waitToPass(async () => { + assert.isTrue(await driver.find('.test-forms-preview').isDisplayed()); + }); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + // Make sure options are there. + assert.deepEqual( + await driver.findAll('select[name="D"] option', e => e.getText()), [CHOOSE_TEXT, 'Foo', 'Bar', 'Baz'] + ); + await driver.findWait('select[name="D"]', 1000).click(); + await driver.find("option[value='Bar']").click(); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectSingle('Bar'); + await removeForm(); }); - await expectSingle(true); - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); - }); - await expectInD([true, false]); - - // Remove the additional record added just now. - await gu.sendActions([ - ['RemoveRecord', 'Table1', 2], - ]); - await removeForm(); - }); - it('can submit a form with ChoiceList field', async function() { - const formUrl = await createFormWith('Choice List', true); - // Add some options. - await gu.openColumnPanel(); - - await gu.choicesEditor.edit(); - await gu.choicesEditor.add('Foo'); - await gu.choicesEditor.add('Bar'); - await gu.choicesEditor.add('Baz'); - await gu.choicesEditor.save(); - await gu.toggleSidePanel('right', 'close'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D[]"][value="Foo"]', 1000).click(); - await driver.findWait('input[name="D[]"][value="Baz"]', 1000).click(); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with Integer field', async function() { + const formUrl = await createFormWith('Integer', true); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await gu.sendKeys('1984'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + // Make sure we see the new record. + await expectSingle(1984); + await removeForm(); }); - await expectSingle(['L', 'Foo', 'Baz']); - await removeForm(); - }); - - it('can submit a form with Ref field', async function() { - const formUrl = await createFormWith('Reference', true); - // Add some options. - await gu.openColumnPanel(); - await gu.setRefShowColumn('A'); - // Add 3 records to this table (it is now empty). - await gu.sendActions([ - ['AddRecord', 'Table1', null, {A: 'Foo'}], // id 1 - ['AddRecord', 'Table1', null, {A: 'Bar'}], // id 2 - ['AddRecord', 'Table1', null, {A: 'Baz'}], // id 3 - ]); - await gu.toggleSidePanel('right', 'close'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - assert.deepEqual( - await driver.findAll('select[name="D"] option', e => e.getText()), - [CHOOSE_TEXT, ...['Bar', 'Baz', 'Foo']] - ); - assert.deepEqual( - await driver.findAll('select[name="D"] option', e => e.value()), - ['', ...['2', '3', '1']] - ); - await driver.findWait('select[name="D"]', 1000).click(); - await driver.find('option[value="2"]').click(); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with Toggle field', async function() { + const formUrl = await createFormWith('Toggle', true); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).findClosest("label").click(); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectSingle(true); + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectInD([true, false]); + + // Remove the additional record added just now. + await gu.sendActions([ + ['RemoveRecord', 'Table1', 2], + ]); + await removeForm(); }); - await expectInD([0, 0, 0, 2]); - // Remove 3 records. - await gu.sendActions([ - ['BulkRemoveRecord', 'Table1', [1, 2, 3, 4]], - ]); - - await removeForm(); - }); - - it('can submit a form with RefList field', async function() { - const formUrl = await createFormWith('Reference List', true); - // Add some options. - await gu.openColumnPanel(); - - await gu.setRefShowColumn('A'); - // Add 3 records to this table (it is now empty). - await gu.sendActions([ - ['AddRecord', 'Table1', null, {A: 'Foo'}], // id 1 - ['AddRecord', 'Table1', null, {A: 'Bar'}], // id 2 - ['AddRecord', 'Table1', null, {A: 'Baz'}], // id 3 - ]); - await gu.toggleSidePanel('right', 'close'); - // We are in a new window. - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D[]"][value="1"]', 1000).click(); - await driver.findWait('input[name="D[]"][value="2"]', 1000).click(); - assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="1"])').getText(), 'Foo'); - assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="2"])').getText(), 'Bar'); - assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="3"])').getText(), 'Baz'); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + it('can submit a form with ChoiceList field', async function() { + const formUrl = await createFormWith('Choice List', true); + // Add some options. + await gu.openColumnPanel(); + + await gu.choicesEditor.edit(); + await gu.choicesEditor.add('Foo'); + await gu.choicesEditor.add('Bar'); + await gu.choicesEditor.add('Baz'); + await gu.choicesEditor.save(); + await gu.toggleSidePanel('right', 'close'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D[]"][value="Foo"]', 1000).click(); + await driver.findWait('input[name="D[]"][value="Baz"]', 1000).click(); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectSingle(['L', 'Foo', 'Baz']); + + await removeForm(); }); - await expectInD([null, null, null, ['L', 2, 1]]); - // Remove 3 records. - await gu.sendActions([ - ['BulkRemoveRecord', 'Table1', [1, 2, 3, 4]], - ]); - - await removeForm(); - }); - - it('can submit a form with a formula field', async function() { - const formUrl = await createFormWith('Text'); + it('can submit a form with Ref field', async function() { + const formUrl = await createFormWith('Reference', true); + // Add some options. + await gu.openColumnPanel(); + await gu.setRefShowColumn('A'); + // Add 3 records to this table (it is now empty). + await gu.sendActions([ + ['AddRecord', 'Table1', null, {A: 'Foo'}], // id 1 + ['AddRecord', 'Table1', null, {A: 'Bar'}], // id 2 + ['AddRecord', 'Table1', null, {A: 'Baz'}], // id 3 + ]); + await gu.toggleSidePanel('right', 'close'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + assert.deepEqual( + await driver.findAll('select[name="D"] option', e => e.getText()), + [CHOOSE_TEXT, ...['Bar', 'Baz', 'Foo']] + ); + assert.deepEqual( + await driver.findAll('select[name="D"] option', e => e.value()), + ['', ...['2', '3', '1']] + ); + await driver.findWait('select[name="D"]', 1000).click(); + await driver.find('option[value="2"]').click(); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectInD([0, 0, 0, 2]); + + // Remove 3 records. + await gu.sendActions([ + ['BulkRemoveRecord', 'Table1', [1, 2, 3, 4]], + ]); - // Temporarily make A a formula column. - await gu.sendActions([ - ['AddRecord', 'Table1', null, {A: 'Foo'}], - ['UpdateRecord', '_grist_Tables_column', 2, {formula: '"hello"', isFormula: true}], - ]); - assert.deepEqual(await api.getTable(docId, 'Table1').then(t => t.A), ['hello']); - - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000).click(); - await gu.sendKeys('Hello World'); - await driver.find('input[name="_A"]').click(); - await gu.sendKeys('goodbye'); - await driver.find('input[type="submit"]').click(); - await waitForConfirm(); + await removeForm(); }); - // Make sure we see the new record. - await expectInD(['', 'Hello World']); + it('can submit a form with RefList field', async function() { + const formUrl = await createFormWith('Reference List', true); + // Add some options. + await gu.openColumnPanel(); + + await gu.setRefShowColumn('A'); + // Add 3 records to this table (it is now empty). + await gu.sendActions([ + ['AddRecord', 'Table1', null, {A: 'Foo'}], // id 1 + ['AddRecord', 'Table1', null, {A: 'Bar'}], // id 2 + ['AddRecord', 'Table1', null, {A: 'Baz'}], // id 3 + ]); + await gu.toggleSidePanel('right', 'close'); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D[]"][value="1"]', 1000).click(); + await driver.findWait('input[name="D[]"][value="2"]', 1000).click(); + assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="1"])').getText(), 'Foo'); + assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="2"])').getText(), 'Bar'); + assert.equal(await driver.find('.grist-checkbox:has(input[name="D[]"][value="3"])').getText(), 'Baz'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + await expectInD([null, null, null, ['L', 2, 1]]); + + // Remove 3 records. + await gu.sendActions([ + ['BulkRemoveRecord', 'Table1', [1, 2, 3, 4]], + ]); - // And check that A was not modified. - assert.deepEqual(await api.getTable(docId, 'Table1').then(t => t.A), ['hello', 'hello']); + await removeForm(); + }); - await gu.sendActions([ - ['RemoveRecord', 'Table1', 1], - ['UpdateRecord', '_grist_Tables_column', 2, {formula: '', isFormula: false}], - ]); - await removeForm(); - }); + it('can submit a form with a formula field', async function() { + const formUrl = await createFormWith('Text'); - it('can unpublish forms', async function() { - const formUrl = await createFormWith('Text'); - await driver.find('.test-forms-unpublish').click(); - await driver.find('.test-modal-confirm').click(); - await gu.waitForServer(); - await gu.onNewTab(async () => { - await driver.get(formUrl); - assert.match( - await driver.findWait('.test-error-text', 2000).getText(), - /Oops! This form is no longer published\./ - ); + // Temporarily make A a formula column. + await gu.sendActions([ + ['AddRecord', 'Table1', null, {A: 'Foo'}], + ['UpdateRecord', '_grist_Tables_column', 2, {formula: '"hello"', isFormula: true}], + ]); + assert.deepEqual(await api.getTable(docId, 'Table1').then(t => t.A), ['hello']); + + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await gu.sendKeys('Hello World'); + await driver.find('input[name="_A"]').click(); + await gu.sendKeys('goodbye'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + + // Make sure we see the new record. + await expectInD(['', 'Hello World']); + + // And check that A was not modified. + assert.deepEqual(await api.getTable(docId, 'Table1').then(t => t.A), ['hello', 'hello']); + + await gu.sendActions([ + ['RemoveRecord', 'Table1', 1], + ['UpdateRecord', '_grist_Tables_column', 2, {formula: '', isFormula: false}], + ]); + await removeForm(); }); - // Republish the form and check that the same URL works again. - await driver.find('.test-forms-publish').click(); - await driver.find('.test-modal-confirm').click(); - await gu.waitForServer(); - await gu.onNewTab(async () => { - await driver.get(formUrl); - await driver.findWait('input[name="D"]', 1000); + it('can unpublish forms', async function() { + const formUrl = await createFormWith('Text'); + await driver.find('.test-forms-unpublish').click(); + await driver.find('.test-modal-confirm').click(); + await gu.waitForServer(); + await gu.onNewTab(async () => { + await driver.get(formUrl); + assert.match( + await driver.findWait('.test-error-text', 2000).getText(), + /Oops! This form is no longer published\./ + ); + }); + + // Republish the form and check that the same URL works again. + await driver.find('.test-forms-publish').click(); + await driver.find('.test-modal-confirm').click(); + await gu.waitForServer(); + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000); + }); }); - }); - it('can stop showing warning when publishing or unpublishing', async function() { - // Click "Don't show again" in both modals and confirm. - await driver.find('.test-forms-unpublish').click(); - await driver.find('.test-modal-dont-show-again').click(); - await driver.find('.test-modal-confirm').click(); - await gu.waitForServer(); - await driver.find('.test-forms-publish').click(); - await driver.find('.test-modal-dont-show-again').click(); - await driver.find('.test-modal-confirm').click(); - await gu.waitForServer(); - - // Check that the modals are no longer shown when publishing or unpublishing. - await driver.find('.test-forms-unpublish').click(); - await gu.waitForServer(); - assert.isFalse(await driver.find('.test-modal-title').isPresent()); - await driver.find('.test-forms-publish').click(); - await gu.waitForServer(); - assert.isFalse(await driver.find('.test-modal-title').isPresent()); - }); - - it('can create a form for a blank table', async function() { + it('can stop showing warning when publishing or unpublishing', async function() { + // Click "Don't show again" in both modals and confirm. + await driver.find('.test-forms-unpublish').click(); + await driver.find('.test-modal-dont-show-again').click(); + await driver.find('.test-modal-confirm').click(); + await gu.waitForServer(); + await driver.find('.test-forms-publish').click(); + await driver.find('.test-modal-dont-show-again').click(); + await driver.find('.test-modal-confirm').click(); + await gu.waitForServer(); - // Add new page and select form. - await gu.addNewPage('Form', 'New Table', { - tableName: 'Form' + // Check that the modals are no longer shown when publishing or unpublishing. + await driver.find('.test-forms-unpublish').click(); + await gu.waitForServer(); + assert.isFalse(await driver.find('.test-modal-title').isPresent()); + await driver.find('.test-forms-publish').click(); + await gu.waitForServer(); + assert.isFalse(await driver.find('.test-modal-title').isPresent()); }); - // Make sure we see a form editor. - assert.isTrue(await driver.find('.test-forms-editor').isDisplayed()); + it('can create a form for a blank table', async function() { - // With 3 questions A, B, C. - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + // Add new page and select form. + await gu.addNewPage('Form', 'New Table', { + tableName: 'Form' + }); - // And a submit button. - assert.isTrue(await driver.findContent('.test-forms-submit', gu.exactMatch('Submit')).isDisplayed()); - }); + // Make sure we see a form editor. + assert.isTrue(await driver.find('.test-forms-editor').isDisplayed()); - it("doesn't generate fields when they are added", async function() { - await gu.sendActions([ - ['AddVisibleColumn', 'Form', 'Choice', - {type: 'Choice', widgetOption: JSON.stringify({choices: ['A', 'B', 'C']})}], - ]); - - // Make sure we see a form editor. - assert.isTrue(await driver.find('.test-forms-editor').isDisplayed()); - await driver.sleep(100); - assert.isFalse( - await driver.findContent('.test-forms-question-choice .test-forms-label', gu.exactMatch('Choice')).isPresent() - ); - }); + // With 3 questions A, B, C. + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - it('supports basic drag and drop', async function() { + // And a submit button. + assert.isTrue(await driver.findContent('.test-forms-submit', gu.exactMatch('Submit')).isDisplayed()); + }); - // Make sure the order is right. - assert.deepEqual( - await readLabels(), ['A', 'B', 'C'] - ); + it("doesn't generate fields when they are added", async function() { + await gu.sendActions([ + ['AddVisibleColumn', 'Form', 'Choice', + {type: 'Choice', widgetOption: JSON.stringify({choices: ['A', 'B', 'C']})}], + ]); - await driver.withActions(a => - a.move({origin: questionDrag('B')}) - .press() - .move({origin: questionDrag('A')}) - .release() - ); + // Make sure we see a form editor. + assert.isTrue(await driver.find('.test-forms-editor').isDisplayed()); + await driver.sleep(100); + assert.isFalse( + await driver.findContent('.test-forms-question-choice .test-forms-label', gu.exactMatch('Choice')).isPresent() + ); + }); - await gu.waitForServer(); + it('supports basic drag and drop', async function() { - // Make sure the order is right. - assert.deepEqual( - await readLabels(), ['B', 'A', 'C'] - ); + // Make sure the order is right. + assert.deepEqual( + await readLabels(), ['A', 'B', 'C'] + ); - await driver.withActions(a => - a.move({origin: questionDrag('C')}) - .press() - .move({origin: questionDrag('B')}) - .release() - ); + await driver.withActions(a => + a.move({origin: questionDrag('B')}) + .press() + .move({origin: questionDrag('A')}) + .release() + ); - await gu.waitForServer(); + await gu.waitForServer(); - // Make sure the order is right. - assert.deepEqual( - await readLabels(), ['C', 'B', 'A'] - ); + // Make sure the order is right. + assert.deepEqual( + await readLabels(), ['B', 'A', 'C'] + ); - // Now move A on A and make sure nothing changes. - await driver.withActions(a => - a.move({origin: questionDrag('A')}) - .press() - .move({origin: questionDrag('A'), x: 50}) - .release() - ); + await driver.withActions(a => + a.move({origin: questionDrag('C')}) + .press() + .move({origin: questionDrag('B')}) + .release() + ); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['C', 'B', 'A']); - }); + await gu.waitForServer(); - it('can undo drag and drop', async function() { - await gu.undo(); - assert.deepEqual(await readLabels(), ['B', 'A', 'C']); + // Make sure the order is right. + assert.deepEqual( + await readLabels(), ['C', 'B', 'A'] + ); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - }); + // Now move A on A and make sure nothing changes. + await driver.withActions(a => + a.move({origin: questionDrag('A')}) + .press() + .move({origin: questionDrag('A'), x: 50}) + .release() + ); - it('adds new question at the end', async function() { - // We should see single drop zone. - assert.equal((await drops()).length, 1); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['C', 'B', 'A']); + }); - // Move the A over there. - await driver.withActions(a => - a.move({origin: questionDrag('A')}) - .press() - .move({origin: plusButton().drag()}) - .release() - ); + it('can undo drag and drop', async function() { + await gu.undo(); + assert.deepEqual(await readLabels(), ['B', 'A', 'C']); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['B', 'C', 'A']); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); - // Now add a new question. - await plusButton().click(); + it('adds new question at the end', async function() { + // We should see single drop zone. + assert.equal((await drops()).length, 1); - await clickMenu('Text'); - await gu.waitForServer(); + // Move the A over there. + await driver.withActions(a => + a.move({origin: questionDrag('A')}) + .press() + .move({origin: plusButton().drag()}) + .release() + ); - // We should have new column D or type text. - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - assert.equal(await questionType('D'), 'Text'); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['B', 'C', 'A']); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - }); + // Now add a new question. + await plusButton().click(); - it('adds question in the middle', async function() { - await driver.withActions(a => a.contextClick(question('B'))); - await clickMenu('Insert question above'); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); + await clickMenu('Text'); + await gu.waitForServer(); - // Now below C. - await driver.withActions(a => a.contextClick(question('B'))); - await clickMenu('Insert question below'); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'E', 'C']); + // We should have new column D or type text. + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + assert.equal(await questionType('D'), 'Text'); - // Make sure they are draggable. - // Move D infront of C. - await driver.withActions(a => - a.move({origin: questionDrag('D')}) - .press() - .move({origin: questionDrag('C')}) - .release() - ); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'B', 'E', 'D', 'C']); + it('adds question in the middle', async function() { + await driver.withActions(a => a.contextClick(question('B'))); + await clickMenu('Insert question above'); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); - // Remove 3 times. - await gu.undo(3); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - }); + // Now below C. + await driver.withActions(a => a.contextClick(question('B'))); + await clickMenu('Insert question below'); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'E', 'C']); + + // Make sure they are draggable. + // Move D infront of C. + await driver.withActions(a => + a.move({origin: questionDrag('D')}) + .press() + .move({origin: questionDrag('C')}) + .release() + ); - it('selection works', async function() { + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'B', 'E', 'D', 'C']); - // Click on A. - await question('A').click(); + // Remove 3 times. + await gu.undo(3); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); - // Now A is selected. - assert.equal(await selectedLabel(), 'A'); + it('selection works', async function() { - // Click B. - await question('B').click(); + // Click on A. + await question('A').click(); - // Now B is selected. - assert.equal(await selectedLabel(), 'B'); + // Now A is selected. + assert.equal(await selectedLabel(), 'A'); - // Click on the edit button. - await driver.find('.test-forms-submit').click(); + // Click B. + await question('B').click(); - // Now nothing is selected. - assert.isFalse(await isSelected(), 'Something is selected'); + // Now B is selected. + assert.equal(await selectedLabel(), 'B'); - // When we add new question, it is automatically selected. - await plusButton().click(); - await clickMenu('Text'); - await gu.waitForServer(); - // Now D is selected. - assert.equal(await selectedLabel(), 'D'); + // Click on the edit button. + await driver.find('.test-forms-submit').click(); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - await question('A').click(); - }); + // Now nothing is selected. + assert.isFalse(await isSelected(), 'Something is selected'); - it('hiding and revealing works', async function() { - await gu.toggleSidePanel('left', 'close'); - await gu.openWidgetPanel(); + // When we add new question, it is automatically selected. + await plusButton().click(); + await clickMenu('Text'); + await gu.waitForServer(); + // Now D is selected. + assert.equal(await selectedLabel(), 'D'); - // We have only one hidden column. - assert.deepEqual(await hiddenColumns(), ['Choice']); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + await question('A').click(); + }); - // Make sure we see it in the menu. - await plusButton().click(); + it('hiding and revealing works', async function() { + await gu.toggleSidePanel('left', 'close'); + await gu.openWidgetPanel(); - // We have 1 unmapped menu item. - assert.equal(await elementCount('menu-unmapped'), 1); + // We have only one hidden column. + assert.deepEqual(await hiddenColumns(), ['Choice']); - // Now move it to the form on B - await driver.withActions(a => - a.move({origin: hiddenColumn('Choice')}) - .press() - .move({origin: questionDrag('B')}) - .release() - ); - await gu.waitForServer(); + // Make sure we see it in the menu. + await plusButton().click(); - // It should be after A. - await gu.waitToPass(async () => { - assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); - }, 500); + // We have 1 unmapped menu item. + assert.equal(await elementCount('menu-unmapped'), 1); - // Undo to make sure it is bundled. - await gu.undo(); + // Now move it to the form on B + await driver.withActions(a => + a.move({origin: hiddenColumn('Choice')}) + .press() + .move({origin: questionDrag('B')}) + .release() + ); + await gu.waitForServer(); - // It should be hidden again. - assert.deepEqual(await hiddenColumns(), ['Choice']); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + // It should be after A. + await gu.waitToPass(async () => { + assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); + }, 500); - // And redo. - await gu.redo(); - assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); - assert.deepEqual(await hiddenColumns(), []); + // Undo to make sure it is bundled. + await gu.undo(); + // It should be hidden again. + assert.deepEqual(await hiddenColumns(), ['Choice']); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // Now hide it using menu. - await question('Choice').rightClick(); - await clickMenu('Hide'); - await gu.waitForServer(); + // And redo. + await gu.redo(); + assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); + assert.deepEqual(await hiddenColumns(), []); - // It should be hidden again. - assert.deepEqual(await hiddenColumns(), ['Choice']); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // And undo. - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); - assert.deepEqual(await hiddenColumns(), []); + // Now hide it using menu. + await question('Choice').rightClick(); + await clickMenu('Hide'); + await gu.waitForServer(); - // And redo. - await gu.redo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - assert.deepEqual(await hiddenColumns(), ['Choice']); + // It should be hidden again. + assert.deepEqual(await hiddenColumns(), ['Choice']); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // Now unhide it using menu. - await plusButton().click(); - await element('menu-unmapped').click(); - await gu.waitForServer(); + // And undo. + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'Choice', 'B', 'C']); + assert.deepEqual(await hiddenColumns(), []); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'Choice']); - assert.deepEqual(await hiddenColumns(), []); + // And redo. + await gu.redo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + assert.deepEqual(await hiddenColumns(), ['Choice']); - // Now hide it using Delete key. - await driver.find('.test-forms-submit').click(); - await question('Choice').click(); - await gu.sendKeys(Key.DELETE); - await gu.waitForServer(); + // Now unhide it using menu. + await plusButton().click(); + await element('menu-unmapped').click(); + await gu.waitForServer(); - // It should be hidden again. - assert.deepEqual(await hiddenColumns(), ['Choice']); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'Choice']); + assert.deepEqual(await hiddenColumns(), []); + // Now hide it using Delete key. + await driver.find('.test-forms-submit').click(); + await question('Choice').click(); + await gu.sendKeys(Key.DELETE); + await gu.waitForServer(); - await gu.toggleSidePanel('right', 'close'); - }); + // It should be hidden again. + assert.deepEqual(await hiddenColumns(), ['Choice']); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - it('basic keyboard navigation works', async function() { - await question('A').click(); - assert.equal(await selectedLabel(), 'A'); - - // Move down. - await arrow(Key.ARROW_DOWN); - assert.equal(await selectedLabel(), 'B'); - - // Move up. - await arrow(Key.ARROW_UP); - assert.equal(await selectedLabel(), 'A'); - - // Move down to C. - await arrow(Key.ARROW_DOWN, 2); - assert.equal(await selectedLabel(), 'C'); - - // Move down we should be at A (past the submit button, and titles and sections). - await arrow(Key.ARROW_DOWN, 7); - assert.equal(await selectedLabel(), 'A'); - - // Do the same with Left and Right. - await arrow(Key.ARROW_RIGHT); - assert.equal(await selectedLabel(), 'B'); - await arrow(Key.ARROW_LEFT); - assert.equal(await selectedLabel(), 'A'); - await arrow(Key.ARROW_RIGHT, 2); - assert.equal(await selectedLabel(), 'C'); - }); - it('cutting works', async function() { - const revert = await gu.begin(); - await question('A').click(); - // Send copy command. - await clipboard.lockAndPerform(async (cb) => { - await cb.cut(); - await gu.sendKeys(Key.ARROW_DOWN); // Focus on B. - await gu.sendKeys(Key.ARROW_DOWN); // Focus on C. - await cb.paste(); + await gu.toggleSidePanel('right', 'close'); }); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['B', 'A', 'C']); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // To the same for paragraph. - await plusButton().click(); - await clickMenu('Paragraph'); - await gu.waitForServer(); - await element('Paragraph', 5).click(); - await clipboard.lockAndPerform(async (cb) => { - await cb.cut(); - // Go over A and paste there. - await gu.sendKeys(Key.ARROW_UP); // Focus on C. - await gu.sendKeys(Key.ARROW_UP); // Focus on B. - await gu.sendKeys(Key.ARROW_UP); // Focus on A. - await cb.paste(); + it('basic keyboard navigation works', async function() { + await question('A').click(); + assert.equal(await selectedLabel(), 'A'); + + // Move down. + await arrow(Key.ARROW_DOWN); + assert.equal(await selectedLabel(), 'B'); + + // Move up. + await arrow(Key.ARROW_UP); + assert.equal(await selectedLabel(), 'A'); + + // Move down to C. + await arrow(Key.ARROW_DOWN, 2); + assert.equal(await selectedLabel(), 'C'); + + // Move down we should be at A (past the submit button, and titles and sections). + await arrow(Key.ARROW_DOWN, 7); + assert.equal(await selectedLabel(), 'A'); + + // Do the same with Left and Right. + await arrow(Key.ARROW_RIGHT); + assert.equal(await selectedLabel(), 'B'); + await arrow(Key.ARROW_LEFT); + assert.equal(await selectedLabel(), 'A'); + await arrow(Key.ARROW_RIGHT, 2); + assert.equal(await selectedLabel(), 'C'); }); - await gu.waitForServer(); - // Paragraph should be the first one now. - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - let elements = await driver.findAll('.test-forms-element'); - assert.isTrue(await elements[0].matches('.test-forms-Paragraph')); - assert.isTrue(await elements[1].matches('.test-forms-Paragraph')); - assert.isTrue(await elements[2].matches('.test-forms-Section')); - assert.isTrue(await elements[3].matches('.test-forms-Paragraph')); - assert.isTrue(await elements[4].matches('.test-forms-Paragraph')); - assert.isTrue(await elements[5].matches('.test-forms-Paragraph')); - - // Put it back using undo. - await gu.undo(); - elements = await driver.findAll('.test-forms-element'); - assert.isTrue(await elements[5].matches('.test-forms-Field')); - // 0 - A, 1 - B, 2 - C, 3 - submit button. - assert.isTrue(await elements[8].matches('.test-forms-Paragraph')); - - await revert(); - }); + it('cutting works', async function() { + const revert = await gu.begin(); + await question('A').click(); + // Send copy command. + await clipboard.lockAndPerform(async (cb) => { + await cb.cut(); + await gu.sendKeys(Key.ARROW_DOWN); // Focus on B. + await gu.sendKeys(Key.ARROW_DOWN); // Focus on C. + await cb.paste(); + }); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['B', 'A', 'C']); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - const checkInitial = async () => assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - const checkNewCol = async () => { - assert.equal(await selectedLabel(), 'D'); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - await gu.undo(); - await checkInitial(); - }; - const checkFieldsAtFirstLevel = (menuText: string) => { - it(`can add ${menuText} elements from the menu`, async function() { + // To the same for paragraph. await plusButton().click(); - await clickMenu(menuText); + await clickMenu('Paragraph'); + await gu.waitForServer(); + await element('Paragraph', 5).click(); + await clipboard.lockAndPerform(async (cb) => { + await cb.cut(); + // Go over A and paste there. + await gu.sendKeys(Key.ARROW_UP); // Focus on C. + await gu.sendKeys(Key.ARROW_UP); // Focus on B. + await gu.sendKeys(Key.ARROW_UP); // Focus on A. + await cb.paste(); + }); await gu.waitForServer(); - await checkNewCol(); - }); - }; - checkFieldsAtFirstLevel('Text'); - checkFieldsAtFirstLevel('Numeric'); - checkFieldsAtFirstLevel('Date'); - checkFieldsAtFirstLevel('Choice'); + // Paragraph should be the first one now. + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + let elements = await driver.findAll('.test-forms-element'); + assert.isTrue(await elements[0].matches('.test-forms-Paragraph')); + assert.isTrue(await elements[1].matches('.test-forms-Paragraph')); + assert.isTrue(await elements[2].matches('.test-forms-Section')); + assert.isTrue(await elements[3].matches('.test-forms-Paragraph')); + assert.isTrue(await elements[4].matches('.test-forms-Paragraph')); + assert.isTrue(await elements[5].matches('.test-forms-Paragraph')); + + // Put it back using undo. + await gu.undo(); + elements = await driver.findAll('.test-forms-element'); + assert.isTrue(await elements[5].matches('.test-forms-Field')); + // 0 - A, 1 - B, 2 - C, 3 - submit button. + assert.isTrue(await elements[8].matches('.test-forms-Paragraph')); - const checkFieldInMore = (menuText: string) => { - it(`can add ${menuText} elements from the menu`, async function() { - await plusButton().click(); - await clickMenu('More'); - await clickMenu(menuText); - await gu.waitForServer(); - await checkNewCol(); + await revert(); }); - }; - - checkFieldInMore('Integer'); - checkFieldInMore('Toggle'); - checkFieldInMore('DateTime'); - checkFieldInMore('Choice List'); - checkFieldInMore('Reference'); - checkFieldInMore('Reference List'); - const testStruct = (type: string, existing = 0) => { - it(`can add structure ${type} element`, async function() { - assert.equal(await elementCount(type), existing); - await plusButton().click(); - await clickMenu(type); - await gu.waitForServer(); - assert.equal(await elementCount(type), existing + 1); + const checkInitial = async () => assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + const checkNewCol = async () => { + assert.equal(await selectedLabel(), 'D'); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); await gu.undo(); - assert.equal(await elementCount(type), existing); - }); - }; + await checkInitial(); + }; + const checkFieldsAtFirstLevel = (menuText: string) => { + it(`can add ${menuText} elements from the menu`, async function() { + await plusButton().click(); + await clickMenu(menuText); + await gu.waitForServer(); + await checkNewCol(); + }); + }; - // testStruct('Section'); // There is already a section - testStruct('Columns'); - testStruct('Paragraph', 4); + checkFieldsAtFirstLevel('Text'); + checkFieldsAtFirstLevel('Numeric'); + checkFieldsAtFirstLevel('Date'); + checkFieldsAtFirstLevel('Choice'); + + const checkFieldInMore = (menuText: string) => { + it(`can add ${menuText} elements from the menu`, async function() { + await plusButton().click(); + await clickMenu('More'); + await clickMenu(menuText); + await gu.waitForServer(); + await checkNewCol(); + }); + }; - it('basic section', async function() { - const revert = await gu.begin(); + checkFieldInMore('Integer'); + checkFieldInMore('Toggle'); + checkFieldInMore('DateTime'); + checkFieldInMore('Choice List'); + checkFieldInMore('Reference'); + checkFieldInMore('Reference List'); + + const testStruct = (type: string, existing = 0) => { + it(`can add structure ${type} element`, async function() { + assert.equal(await elementCount(type), existing); + await plusButton().click(); + await clickMenu(type); + await gu.waitForServer(); + assert.equal(await elementCount(type), existing + 1); + await gu.undo(); + assert.equal(await elementCount(type), existing); + }); + }; - // Adding section is disabled for now, so this test is altered to use the existing section. - // await drop().click(); - // await clickMenu('Section'); - // await gu.waitForServer(); - assert.equal(await elementCount('Section'), 1); + // testStruct('Section'); // There is already a section + testStruct('Columns'); + testStruct('Paragraph', 4); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + it('basic section', async function() { + const revert = await gu.begin(); - // There is a drop in that section, click it to add a new question. - await element('Section', 1).element('plus').click(); - await clickMenu('Text'); - await gu.waitForServer(); + // Adding section is disabled for now, so this test is altered to use the existing section. + // await drop().click(); + // await clickMenu('Section'); + // await gu.waitForServer(); + assert.equal(await elementCount('Section'), 1); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // And the question is inside a section. - assert.equal(await element('Section', 1).element('label', 4).getText(), 'D'); + // There is a drop in that section, click it to add a new question. + await element('Section', 1).element('plus').click(); + await clickMenu('Text'); + await gu.waitForServer(); - // Make sure we can move that question around. - await driver.withActions(a => - a.move({origin: questionDrag('D')}) - .press() - .move({origin: questionDrag('B')}) - .release() - ); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); + // And the question is inside a section. + assert.equal(await element('Section', 1).element('label', 4).getText(), 'D'); - // Make sure that it is not inside the section anymore. - // assert.equal(await element('Section', 1).element('label').isPresent(), false); + // Make sure we can move that question around. + await driver.withActions(a => + a.move({origin: questionDrag('D')}) + .press() + .move({origin: questionDrag('B')}) + .release() + ); - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - assert.equal(await element('Section', 1).element('label', 4).getText(), 'D'); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); - await revert(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - }); + // Make sure that it is not inside the section anymore. + // assert.equal(await element('Section', 1).element('label').isPresent(), false); - it('basic columns work', async function() { - const revert = await gu.begin(); + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + assert.equal(await element('Section', 1).element('label', 4).getText(), 'D'); - // Open the creator panel to make sure it works. - await gu.openColumnPanel(); + await revert(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); - await plusButton().click(); - await clickMenu('Columns'); - await gu.waitForServer(); + it('basic columns work', async function() { + const revert = await gu.begin(); - // We have two placeholders for free. - assert.equal(await elementCount('Placeholder', element('Columns')), 2); + // Open the creator panel to make sure it works. + await gu.openColumnPanel(); - // We can add another placeholder - await element('add').click(); - await gu.waitForServer(); + await plusButton().click(); + await clickMenu('Columns'); + await gu.waitForServer(); - // Now we have 3 placeholders. - assert.equal(await elementCount('Placeholder', element('Columns')), 3); + // We have two placeholders for free. + assert.equal(await elementCount('Placeholder', element('Columns')), 2); - // We can click the middle one, and add a question. - await element('Columns').element(`Placeholder`, 2).click(); - await clickMenu('Text'); - await gu.waitForServer(); + // We can add another placeholder + await element('add').click(); + await gu.waitForServer(); - // Now we have 2 placeholders - assert.equal(await elementCount('Placeholder', element('Columns')), 2); - // And 4 questions. - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + // Now we have 3 placeholders. + assert.equal(await elementCount('Placeholder', element('Columns')), 3); - // The question D is in the columns. - assert.equal(await element('Columns').element('label').getText(), 'D'); + // We can click the middle one, and add a question. + await element('Columns').element(`Placeholder`, 2).click(); + await clickMenu('Text'); + await gu.waitForServer(); - // We can move it around. - await driver.withActions(a => - a.move({origin: questionDrag('D')}) - .press() - .move({origin: questionDrag('B')}) - .release() - ); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); - - // And move it back. - await driver.withActions(a => - a.move({origin: questionDrag('D')}) - .press() - .move({origin: element('Columns').element(`Placeholder`, 2).find(`.test-forms-drag`)}) - .release() - ); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + // Now we have 2 placeholders + assert.equal(await elementCount('Placeholder', element('Columns')), 2); + // And 4 questions. + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + // The question D is in the columns. + assert.equal(await element('Columns').element('label').getText(), 'D'); - assert.equal(await elementCount('column'), 3); - assert.equal(await element('column', 1).type(), 'Placeholder'); - assert.equal(await element('column', 2).type(), 'Field'); - assert.equal(await element('column', 2).element('label').getText(), 'D'); - assert.equal(await element('column', 3).type(), 'Placeholder'); + // We can move it around. + await driver.withActions(a => + a.move({origin: questionDrag('D')}) + .press() + .move({origin: questionDrag('B')}) + .release() + ); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'D', 'B', 'C']); + + // And move it back. + await driver.withActions(a => + a.move({origin: questionDrag('D')}) + .press() + .move({origin: element('Columns').element(`Placeholder`, 2).find(`.test-forms-drag`)}) + .release() + ); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - // Check that we can remove the question. - await question('D').rightClick(); - await clickMenu('Hide'); - await gu.waitForServer(); - // Now we have 3 placeholders. - assert.equal(await elementCount('Placeholder', element('Columns')), 3); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + assert.equal(await elementCount('column'), 3); + assert.equal(await element('column', 1).type(), 'Placeholder'); + assert.equal(await element('column', 2).type(), 'Field'); + assert.equal(await element('column', 2).element('label').getText(), 'D'); + assert.equal(await element('column', 3).type(), 'Placeholder'); - // Undo and check it goes back at the right place. - await gu.undo(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + // Check that we can remove the question. + await question('D').rightClick(); + await clickMenu('Hide'); + await gu.waitForServer(); - assert.equal(await elementCount('column'), 3); - assert.equal(await element('column', 1).type(), 'Placeholder'); - assert.equal(await element('column', 2).type(), 'Field'); - assert.equal(await element('column', 2).element('label').getText(), 'D'); - assert.equal(await element('column', 3).type(), 'Placeholder'); + // Now we have 3 placeholders. + assert.equal(await elementCount('Placeholder', element('Columns')), 3); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - // There was a bug with paragraph and columns. - // Add a paragraph to first placeholder. - await element('Columns').element(`Placeholder`, 1).click(); - await clickMenu('Paragraph'); - await gu.waitForServer(); + // Undo and check it goes back at the right place. + await gu.undo(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + + assert.equal(await elementCount('column'), 3); + assert.equal(await element('column', 1).type(), 'Placeholder'); + assert.equal(await element('column', 2).type(), 'Field'); + assert.equal(await element('column', 2).element('label').getText(), 'D'); + assert.equal(await element('column', 3).type(), 'Placeholder'); + + // There was a bug with paragraph and columns. + // Add a paragraph to first placeholder. + await element('Columns').element(`Placeholder`, 1).click(); + await clickMenu('Paragraph'); + await gu.waitForServer(); - // Now click this paragraph. - await element('Columns').element(`Paragraph`, 1).click(); - // And make sure there aren't any errors. - await gu.checkForErrors(); + // Now click this paragraph. + await element('Columns').element(`Paragraph`, 1).click(); + // And make sure there aren't any errors. + await gu.checkForErrors(); - await revert(); - assert.lengthOf(await driver.findAll('.test-forms-column'), 0); - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - }); + await revert(); + assert.lengthOf(await driver.findAll('.test-forms-column'), 0); + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); - it('drags and drops on columns properly', async function() { - const revert = await gu.begin(); - // Open the creator panel to make sure it works. - await gu.openColumnPanel(); + it('drags and drops on columns properly', async function() { + const revert = await gu.begin(); + // Open the creator panel to make sure it works. + await gu.openColumnPanel(); - await plusButton().click(); - await clickMenu('Columns'); - await gu.waitForServer(); + await plusButton().click(); + await clickMenu('Columns'); + await gu.waitForServer(); - // Make sure that dragging columns on its placeholder doesn't do anything. - await driver.withActions(a => - a.move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) - .press() - .move({origin: element('Columns').element(`Placeholder`, 2).find(`.test-forms-drag`)}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); + // Make sure that dragging columns on its placeholder doesn't do anything. + await driver.withActions(a => + a.move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) + .press() + .move({origin: element('Columns').element(`Placeholder`, 2).find(`.test-forms-drag`)}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + + // Make sure we see form correctly. + const testNothingIsMoved = async () => { + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + assert.deepEqual(await elements(), [ + 'Paragraph', + 'Paragraph', + 'Section', + 'Paragraph', + 'Paragraph', + 'Field', + 'Field', + 'Field', + 'Columns', + 'Placeholder', + 'Placeholder' + ]); + }; + + await testNothingIsMoved(); + + // Now do the same but move atop the + placeholder. + await driver.withActions(a => + a.move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) + .press() + .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + await testNothingIsMoved(); + + // Now move C column into first column. + await driver.withActions(a => + a.move({origin: questionDrag('C')}) + .press() + .move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + + // Check that it worked. + assert.equal(await element('column', 1).type(), 'Field'); + assert.equal(await element('column', 1).element('label').getText(), 'C'); + assert.equal(await element('column', 2).type(), 'Placeholder'); + + // Try to move B over C. + await driver.withActions(a => + a.move({origin: questionDrag('B')}) + .press() + .move({origin: questionDrag('C')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); - // Make sure we see form correctly. - const testNothingIsMoved = async () => { - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); - assert.deepEqual(await elements(), [ - 'Paragraph', - 'Paragraph', - 'Section', - 'Paragraph', - 'Paragraph', - 'Field', - 'Field', - 'Field', - 'Columns', - 'Placeholder', - 'Placeholder' - ]); - }; + // Make sure it didn't work. + assert.equal(await element('column', 1).type(), 'Field'); + assert.equal(await element('column', 1).element('label').getText(), 'C'); - await testNothingIsMoved(); + // And B is still there. + assert.equal(await element('Field', 2).element('label').getText(), 'B'); - // Now do the same but move atop the + placeholder. - await driver.withActions(a => - a.move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) - .press() - .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - await testNothingIsMoved(); - - // Now move C column into first column. - await driver.withActions(a => - a.move({origin: questionDrag('C')}) - .press() - .move({origin: element('Columns').element(`Placeholder`, 1).find(`.test-forms-drag`)}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - - // Check that it worked. - assert.equal(await element('column', 1).type(), 'Field'); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - assert.equal(await element('column', 2).type(), 'Placeholder'); - - // Try to move B over C. - await driver.withActions(a => - a.move({origin: questionDrag('B')}) - .press() - .move({origin: questionDrag('C')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - - // Make sure it didn't work. - assert.equal(await element('column', 1).type(), 'Field'); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - - // And B is still there. - assert.equal(await element('Field', 2).element('label').getText(), 'B'); - - // Now move B on the empty placholder. - await driver.withActions(a => - a.move({origin: questionDrag('B')}) - .press() - .move({origin: element('column', 2).drag()}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - - // Make sure it worked. - assert.equal(await element('column', 1).type(), 'Field'); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - assert.equal(await element('column', 2).type(), 'Field'); - assert.equal(await element('column', 2).element('label').getText(), 'B'); - - // Now swap them moving C over B. - await driver.withActions(a => - a.move({origin: questionDrag('C')}) - .press() - .move({origin: questionDrag('B')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - assert.equal(await element('column', 1).element('label').getText(), 'B'); - assert.equal(await element('column', 2).element('label').getText(), 'C'); - - // And swap them back. - await driver.withActions(a => - a.move({origin: questionDrag('B')}) - .press() - .move({origin: questionDrag('C')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - assert.equal(await element('column', 2).element('label').getText(), 'B'); - - // Make sure we still have two columns only. - assert.lengthOf(await driver.findAll('.test-forms-column'), 2); - - // Make sure draggin column on the add button doesn't add column. - await driver.withActions(a => - a.move({origin: questionDrag('B')}) - .press() - .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - - // Make sure we still have two columns only. - assert.lengthOf(await driver.findAll('.test-forms-column'), 2); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - assert.equal(await element('column', 2).element('label').getText(), 'B'); - - // Now move A over the + button to add a new column. - await driver.withActions(a => - a.move({origin: questionDrag('A')}) - .press() - .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) - .release() - ); - await gu.waitForServer(); - await gu.checkForErrors(); - assert.lengthOf(await driver.findAll('.test-forms-column'), 3); - assert.equal(await element('column', 1).element('label').getText(), 'C'); - assert.equal(await element('column', 2).element('label').getText(), 'B'); - assert.equal(await element('column', 3).element('label').getText(), 'A'); + // Now move B on the empty placholder. + await driver.withActions(a => + a.move({origin: questionDrag('B')}) + .press() + .move({origin: element('column', 2).drag()}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + + // Make sure it worked. + assert.equal(await element('column', 1).type(), 'Field'); + assert.equal(await element('column', 1).element('label').getText(), 'C'); + assert.equal(await element('column', 2).type(), 'Field'); + assert.equal(await element('column', 2).element('label').getText(), 'B'); + + // Now swap them moving C over B. + await driver.withActions(a => + a.move({origin: questionDrag('C')}) + .press() + .move({origin: questionDrag('B')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + assert.equal(await element('column', 1).element('label').getText(), 'B'); + assert.equal(await element('column', 2).element('label').getText(), 'C'); + + // And swap them back. + await driver.withActions(a => + a.move({origin: questionDrag('B')}) + .press() + .move({origin: questionDrag('C')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + assert.equal(await element('column', 1).element('label').getText(), 'C'); + assert.equal(await element('column', 2).element('label').getText(), 'B'); + + // Make sure we still have two columns only. + assert.lengthOf(await driver.findAll('.test-forms-column'), 2); + + // Make sure draggin column on the add button doesn't add column. + await driver.withActions(a => + a.move({origin: questionDrag('B')}) + .press() + .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + + // Make sure we still have two columns only. + assert.lengthOf(await driver.findAll('.test-forms-column'), 2); + assert.equal(await element('column', 1).element('label').getText(), 'C'); + assert.equal(await element('column', 2).element('label').getText(), 'B'); + + // Now move A over the + button to add a new column. + await driver.withActions(a => + a.move({origin: questionDrag('A')}) + .press() + .move({origin: driver.find('.test-forms-Columns .test-forms-add')}) + .release() + ); + await gu.waitForServer(); + await gu.checkForErrors(); + assert.lengthOf(await driver.findAll('.test-forms-column'), 3); + assert.equal(await element('column', 1).element('label').getText(), 'C'); + assert.equal(await element('column', 2).element('label').getText(), 'B'); + assert.equal(await element('column', 3).element('label').getText(), 'A'); - await revert(); - }); + await revert(); + }); - it('changes type of a question', async function() { - // Add text question as D column. - await plusButton().click(); - await clickMenu('Text'); - await gu.waitForServer(); - assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); + it('changes type of a question', async function() { + // Add text question as D column. + await plusButton().click(); + await clickMenu('Text'); + await gu.waitForServer(); + assert.deepEqual(await readLabels(), ['A', 'B', 'C', 'D']); - // Make sure it is a text question. - assert.equal(await questionType('D'), 'Text'); + // Make sure it is a text question. + assert.equal(await questionType('D'), 'Text'); - // Now change it to a choice, from the backend (as the UI is not clear here). - await gu.sendActions([ - ['ModifyColumn', 'Form', 'D', {type: 'Choice', widgetOptions: JSON.stringify({choices: ['A', 'B', 'C']})}], - ]); + // Now change it to a choice, from the backend (as the UI is not clear here). + await gu.sendActions([ + ['ModifyColumn', 'Form', 'D', {type: 'Choice', widgetOptions: JSON.stringify({choices: ['A', 'B', 'C']})}], + ]); - // Make sure it is a choice question. - await gu.waitToPass(async () => { - assert.equal(await questionType('D'), 'Choice'); + // Make sure it is a choice question. + await gu.waitToPass(async () => { + assert.equal(await questionType('D'), 'Choice'); + }); + + // Now change it back to a text question. + await gu.undo(); + await gu.waitToPass(async () => { + assert.equal(await questionType('D'), 'Text'); + }); + + await gu.redo(); + await gu.waitToPass(async () => { + assert.equal(await questionType('D'), 'Choice'); + }); + + await gu.undo(2); + await gu.waitToPass(async () => { + assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + }); }); + }); - // Now change it back to a text question. - await gu.undo(); - await gu.waitToPass(async () => { - assert.equal(await questionType('D'), 'Text'); + describe('on team site', async function() { + before(async function() { + const session = await gu.session().teamSite.login(); + docId = await session.tempNewDoc(cleanup); + api = session.createHomeApi(); + await driver.executeScript(createClipboardTextArea); }); - await gu.redo(); - await gu.waitToPass(async () => { - assert.equal(await questionType('D'), 'Choice'); + after(async function() { + await driver.executeScript(removeClipboardTextArea); }); - await gu.undo(2); - await gu.waitToPass(async () => { - assert.deepEqual(await readLabels(), ['A', 'B', 'C']); + it('can submit a form', async function() { + // A bug was preventing this by forcing a login redirect from the public form URL. + const formUrl = await createFormWith('Text'); + await gu.removeLogin(); + // We are in a new window. + await gu.onNewTab(async () => { + await driver.get(formUrl); + await driver.findWait('input[name="D"]', 1000).click(); + await gu.sendKeys('Hello World'); + await driver.find('input[type="submit"]').click(); + await waitForConfirm(); + }); + // Make sure we see the new record. + const session = await gu.session().teamSite.login(); + await session.loadDoc(`/doc/${docId}`); + await expectSingle('Hello World'); + await removeForm(); }); }); });