From abb6c6e77614081ddd3b897dd6c0e4e50a3b50ea Mon Sep 17 00:00:00 2001 From: Aref Shafaei Date: Thu, 5 Sep 2024 11:45:10 -0700 Subject: [PATCH] add validation tests to testFormPresentationAndValidation --- .../input-switch/date-time-field.tsx | 12 +- .../schema/recordedit/product-add.json | 2 +- test/e2e/locators/modal.ts | 9 + test/e2e/locators/page.ts | 4 + test/e2e/locators/recordedit.ts | 47 +- .../recordedit/add.conf.js | 15 - .../recordedit/add.spec.js | 392 -------------- .../recordedit/add.spec.ts | 59 +- .../multi-form-input-create.spec.ts | 4 +- test/e2e/utils/recordedit-utils.ts | 508 +++++++++++++++--- 10 files changed, 520 insertions(+), 532 deletions(-) delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.conf.js delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.spec.js diff --git a/src/components/input-switch/date-time-field.tsx b/src/components/input-switch/date-time-field.tsx index 76dcf3d20..093875289 100644 --- a/src/components/input-switch/date-time-field.tsx +++ b/src/components/input-switch/date-time-field.tsx @@ -66,7 +66,7 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { const datetimeFieldState = getFieldState(props.name); // if both are missing, the input is empty - if (!dateVal && !timeVal && !props.requiredInput) { + if (!dateVal && !timeVal) { if (datetimeFieldState.error) clearErrors(props.name); setValue(props.name, ''); trigger(props.name); @@ -89,18 +89,14 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { } } - // if only time is missing, use 00:00:00 for it + // if time is missing, use 00:00:00 for it let timeValTemp = ''; - if (!timeVal && !props.requiredInput) { + if (!timeVal) { timeValTemp = '00:00:00'; + setValue(`${props.name}-time`, timeValTemp); } // otherwise validate the time value else { - if (!timeVal) { - setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: 'Please enter a valid time' }); - setValue(props.name, 'invalid-value'); - return; - } const err = VALIDATE_VALUE_BY_TYPE['time'](timeVal); if (typeof err === 'string') { setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: err }); diff --git a/test/e2e/data_setup/schema/recordedit/product-add.json b/test/e2e/data_setup/schema/recordedit/product-add.json index 688faa91e..2fd83ad0d 100644 --- a/test/e2e/data_setup/schema/recordedit/product-add.json +++ b/test/e2e/data_setup/schema/recordedit/product-add.json @@ -216,7 +216,7 @@ "default": null, "nullok": true, "type": { - "typename": "int4" + "typename": "int2" }, "annotations": { "comment" : ["top"], diff --git a/test/e2e/locators/modal.ts b/test/e2e/locators/modal.ts index 3bcd1bac6..356a98d36 100644 --- a/test/e2e/locators/modal.ts +++ b/test/e2e/locators/modal.ts @@ -64,6 +64,10 @@ export default class ModalLocators { return page.locator('.confirm-iframe-close-modal'); } + static getMarkdownPreviewModal(page: Page): Locator { + return page.locator('.chaise-preview-markdown'); + } + // ------------- common modal functions -------------- // static getModalTitle(modal: Locator) { @@ -158,4 +162,9 @@ export default class ModalLocators { static getIframeFieldModalSpinner(modal: Locator): Locator { return modal.locator('.iframe-field-modal-spinner') } + + // --------- markdown-preview modal functions ------------ // + static getMarkdownPreviewContent(modal: Locator): Locator { + return ModalLocators.getModalText(modal).locator('.markdown-container'); + } } diff --git a/test/e2e/locators/page.ts b/test/e2e/locators/page.ts index 25a591864..691fa367c 100644 --- a/test/e2e/locators/page.ts +++ b/test/e2e/locators/page.ts @@ -17,4 +17,8 @@ export default class PageLocators { static getFooterLink(page: Page): Locator { return PageLocators.getFooterContainer(page).locator('a'); } + + static getHelpPageMainTable(page: Page): Locator { + return page.locator('#mainTable') + } } diff --git a/test/e2e/locators/recordedit.ts b/test/e2e/locators/recordedit.ts index 59950ddf4..8bcb71128 100644 --- a/test/e2e/locators/recordedit.ts +++ b/test/e2e/locators/recordedit.ts @@ -39,19 +39,6 @@ export enum RecordeditInputType { TEXT = 'text' } -export enum RecordeditArrayBaseType { - TIMESTAMP = 'timestamp', - DATE = 'date', - INT_2 = 'integer2', - INT_4 = 'integer4', - INT_8 = 'integer8', - NUMBER = 'number', - BOOLEAN = 'boolean', - MARKDOWN = 'markdown', - LONGTEXT = 'longtext', - TEXT = 'text' -} - export default class RecordeditLocators { static async waitForRecordeditPageReady(container: Locator | Page, timeout?: number) { @@ -194,6 +181,12 @@ export default class RecordeditLocators { return container.locator(`.input-switch-container-${inputName}`).locator('.chaise-input-control'); } + static getInputRemoveButton(container: Locator | Page, name: string, formNumber: number): Locator { + formNumber = formNumber || 1; + const inputName = `c_${formNumber}-${name}`; + return container.locator(`.input-switch-container-${inputName}`).locator('.remove-input-btn'); + } + static getErrorMessageForAColumn(container: Locator | Page, name: string, formNumber: number): Locator { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; @@ -383,6 +376,7 @@ export default class RecordeditLocators { } // ------------- array selectors ----------------- // + static getArrayFieldContainer(container: Locator | Page, name: string, formNumber: number) { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; @@ -392,17 +386,38 @@ export default class RecordeditLocators { /** * TODO this only supports array of texts for now and should be changed later for other types. */ - static getArrayFieldElements(container: Locator | Page, name: string, formNumber: number, baseType: string) { + static getArrayFieldElements(container: Locator | Page, name: string, formNumber: number) { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; const elem = container.locator(`.array-input-field-container-${inputName}`); return { container: elem, addItemContainer: elem.locator('.add-element-container'), - addItemInput: elem.locator('.add-element-container input'), addItemButton: elem.locator('.add-button'), removeItemButtons: elem.locator('.array-remove-button'), - inputs: elem.locator('li input') }; } + + static getArrayInputName(name: string, index: number) { + const append = index === -1 ? 'new-item' : `${index}-val`; + return `${name}-${append}`; + } + + + // ------------- markdown selectors ----------------- // + static getMarkdownElements(container: Locator | Page, name: string, formNumber: number) { + formNumber = formNumber || 1; + const inputName = `c_${formNumber}-${name}`; + const elem = container.locator(`.input-switch-container-${inputName}`); + + return { + container: elem, + getButton: (title: string) => elem.locator(`button[title="${title}"]`), + helpButton: elem.locator('button[title="Help"]'), + previewButton: elem.locator('button[title="Preview"]'), + fullScreenButton: elem.locator('button[title="Fullscreen Preview"]'), + previewContent: elem.locator('.md-preview .markdown-container') + } + + } } diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js b/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js deleted file mode 100644 index 30bf968f6..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js +++ /dev/null @@ -1,15 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordedit/add.dev.json', - specs: [ - "add.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - }, - chaiseConfigFilePath: 'test/e2e/specs/all-features-confirmation/chaise-config.js' -}); - -exports.config = config; diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js deleted file mode 100644 index cf59129b1..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js +++ /dev/null @@ -1,392 +0,0 @@ -var testConfiguration = browser.params.configuration; -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'), chance = require('chance').Chance(); -var moment = require('moment'); - -// take a look at the comments in recordedit-helpers.js for the expected structure of tableParams. -var currentTimestampTime = moment().format("x"); -var testParams = { - tables: [{ - comment: "general case", - schema_name: "product-add", - table_name: "accommodation", - table_displayname: "Accommodations", - table_comment: "List of different types of accommodations", - test_results: true, - primary_keys: ["id"], - columns: [ - { name: "id", generated: true, immutable: true, title: "Id", type: "serial4", nullok: false}, - { name: "title", title: "Name of Accommodation", type: "text", nullok: false}, - { name: "website", title: "Website", type: "text", comment: "A valid url of the accommodation"}, - { name: "category", title: "Category", type: "text", nullok: false, isForeignKey: true, count: 5, totalCount: 5, comment: "Type of accommodation (Resort, Hotel, or Motel)"}, // the total count is the total number of rows in the category.json data file - { name: "rating", title: "User Rating", type: "float4", nullok: false}, - { name: "summary", title: "Summary", type: "longtext", nullok: false, inline_comment: "A comment displayed for the summary column"}, - { name: "description", title: "Description", type: "markdown"}, - { name: "json_col", title: "json_col", type:"json"}, - { name: "no_of_rooms", title: "Number of Rooms", type: "int2", inline_comment: 'This shows the number of rooms!'}, - { name: "opened_on", title: "Operational Since", type: "timestamptz", nullok: false }, - { name: "date_col", title: "date_col", type: "date"}, - { name: "luxurious", title: "Is Luxurious", type: "boolean", nullok: false }, - { name: "text_array", title: "text_array", type: "array", baseType: "text" }, - { name: "boolean_array", title: "boolean_array", type: "array", baseType: "boolean" }, - { name: "int4_array", title: "int4_array", type: "array", baseType: "integer" }, - { name: "float4_array", title: "float4_array", type: "array", baseType: "number" }, - { name: "date_array", title: "date_array", type: "array", baseType: "date" }, - { name: "timestamp_array", title: "timestamp_array", type: "array", baseType: "timestamp" }, - { name: "timestamptz_array", title: "timestamptz_array", type: "array", baseType: "timestamptz" }, - { name: "color_rgb_hex_column", title: "color_rgb_hex_column", type: "color", nullok: false } - ], - inputs: [ - { - "title": "new title 1", "website": "https://example1.com", "category": {index: 0, value: "Hotel"}, - "rating": "1", "summary": "This is the summary of this column 1.", "description": "## Description 1", - "json_col": JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "no_of_rooms": "1", "opened_on": moment("2017-01-01 01:01:01", "YYYY-MM-DD hh:mm:ss"), "date_col": "2017-01-01", "luxurious": false, - "text_array": ["v1", "v2"], "boolean_array": [true], "int4_array": [1, 2], "float4_array": [1, 2.2], - "date_array": ["2001-01-01", "2002-02-02"], "timestamp_array": ["2001-01-01T01:01:01"], - "timestamptz_array": ["2001-01-01T01:01:01-08:00"], - "color_rgb_hex_column": "#123456" - }, - { - "title": "new title 2", "website": "https://example2.com", "category": {index: 1, value: "Ranch"}, - "rating": "2", "summary": "This is the summary of this column 2.", "description": "## Description 2", - "json_col": JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "no_of_rooms": "2", "opened_on": moment("2017-02-02 02:02:02", "YYYY-MM-DD hh:mm:ss"), "date_col": "2017-02-02", "luxurious": true, - "text_array": ["v2", "v3"], "boolean_array": [false], "int4_array": [1, 2], "float4_array": [2, 3.3], - "date_array": ["2002-02-02"], "timestamp_array": ["2002-02-02T02:02:02"], - "timestamptz_array": ["2002-02-02T02:02:02-08:00"], - "color_rgb_hex_column": "#654321" - } - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "title", "website", "product-add_fk_category", "rating", "summary", "description", - "json_col", "no_of_rooms", "opened_on", "date_col", "luxurious", - "text_array", "boolean_array", "int4_array", "float4_array", "date_array", "timestamp_array", "timestamptz_array", "color_rgb_hex_column" - ], - results: [ - [ - "new title 1", {"link":"https://example1.com/", "value":"Link to Website"}, {"link":`${process.env.CHAISE_BASE_URL}/record/#${process.env.catalogId}/product-add:category/term=Hotel`, "value":"Hotel"}, - "1.0000", "This is the summary of this column 1.", "Description 1", JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "1", "2017-01-01 01:01:01", "2017-01-01", "false", - "v1, v2", "true", "1, 2", "1.0000, 2.2000", "2001-01-01, 2002-02-02", "2001-01-01 01:01:01", "2001-01-01 01:01:01", "#123456" - ], - [ - "new title 2", {"link":"https://example2.com/", "value":"Link to Website"}, {"link":`${process.env.CHAISE_BASE_URL}/record/#${process.env.catalogId}/product-add:category/term=Ranch`, "value":"Ranch"}, - "2.0000", "This is the summary of this column 2.", "Description 2", JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "2", "2017-02-02 02:02:02", "2017-02-02", "true", - "v2, v3", "false", "1, 2", "2.0000, 3.3000", "2002-02-02", "2002-02-02 02:02:02", "2002-02-02 02:02:02", "#654321" - ] - ], - files: [] - }, - { - comment: "uploading new files", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, - {"fileid": "3", "uri": 2, "timestamp_txt": currentTimestampTime, validate: true} - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/3a8c740953a168d9761d0ba2c9800475:", "value": "testfile1MB.txt"}, "testfile1MB.txt", "1.02 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile1MB.txt", - size: "1024000", - displaySize: "1000 kB", - path: "testfile1MB.txt", - skipDeletion: true, - tooltip: "- testfile1MB.txt\n- 1000 kB" - }, { - name: "testfile500kb.png", - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - skipDeletion: true, - tooltip: "- testfile500kb.png\n- 500 kB" - }, { - name: "testfile5MB.txt", - size: "5242880", - displaySize: "5000 kB", - path: "testfile5MB.txt", - tooltip: "- testfile5MB.txt\n- 5 MB" - }] - }, - { - comment: "uploader when one file exists in hatrac and the other one is new", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, // this is a new file - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "3", "uri": 1, "timestamp_txt": currentTimestampTime} // this form won't be submitted - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/b5dad28809685d9764dbd08fa23600bc:", "value": "testfile10MB_new.txt"}, "testfile10MB_new.txt", "10.2 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile10MB_new.txt", // a new file with new md5 - size: "10240000", - displaySize: "9.77 MB", - path: "testfile10MB_new.txt", - tooltip: "- testfile10MB_new.txt\n- 9.77 MB" - }, { - name: "testfile500kb.png", // using the same file that has been already uploaded - skipCreation: true, - skipDeletion: true, - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - tooltip: "- testfile500kb.png\n- 500 kB" - }] - }, - { - comment: "uploader when all the files already exist in hatrac", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "3", "uri": 1, "timestamp_txt": currentTimestampTime} // this form won't be submitted - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/3a8c740953a168d9761d0ba2c9800475:", "value": "testfile1MB.txt"}, "testfile1MB.txt", "1.02 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile1MB.txt", // using the same file that has been already uploaded - skipCreation: true, - size: "1024000", - displaySize: "1000 kB", - path: "testfile1MB.txt", - tooltip: "- testfile1MB.txt\n- 1000 kB" - }, { - name: "testfile500kb.png", // using the same file that has been already uploaded - skipCreation: true, - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - tooltip: "- testfile500kb.png\n- 500 kB" - }] - } - ] -}; - -// keep track of namespaces that we use, so we can delete them afterwards -if (!process.env.CI) { - testConfiguration.hatracNamespaces.push(process.env.ERMREST_URL.replace("/ermrest", "") + "/hatrac/js/chaise/" + currentTimestampTime); -} - -describe('Record Add', function() { - - for (var i=0; i< testParams.tables.length; i++) { - (function(tableParams, index) { - - describe("======================================================================= \n " - + tableParams.inputs.length + " record(s) for table " + tableParams.table_name + " testing " + tableParams.comment + ",", function() { - - beforeAll(function () { - uri = browser.params.url + "/recordedit/#" + browser.params.catalogId + "/" + tableParams.schema_name + ":" + tableParams.table_name; - // if it's the same table-name, we have to reload. browser.get is not reloading the page - if (index > 0 && tableParams.table_name === testParams.tables[index-1].table_name) { - chaisePage.refresh(uri); - } else { - chaisePage.navigate(uri); - } - chaisePage.recordeditPageReady(); - }); - - describe("Presentation and validation,", function() { - - if (!process.env.CI && tableParams.files.length > 0) { - beforeAll(function() { - // create files that will be uploaded - recordEditHelpers.createFiles(tableParams.files); - console.log("\n"); - }); - } - - recordEditHelpers.testPresentationAndBasicValidation(tableParams, false); - }); - - describe("remove record, ", function() { - - if (tableParams.inputs.length > 1) { - - if (tableParams.files.length == 0) { - it("should click and add an extra record.", function(done) { - chaisePage.clickButton(chaisePage.recordEditPage.getCloneFormInputSubmitButton()).then(function () { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - } - - it((tableParams.formsAfterInput) + " buttons should be visible and enabled", function(done) { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - expect(buttons.length).toBe(tableParams.formsAfterInput); - buttons.forEach(function(btn) { - expect(btn.isDisplayed()).toBe(true); - expect(btn.isEnabled()).toBe(true); - }); - - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it("should click and remove the last record", function(done) { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - // NOTE: chaisePage.clickButton() was clicking the submit button instead of the remove form button - // if this keeps happening after this change, scroll button into view then click - return buttons[tableParams.formsAfterInput - 1].click(); - }).then(function () { - return chaisePage.recordEditPage.getAllDeleteRowButtons(); - }).then(function(buttons) { - expect(buttons.length).toBe(tableParams.formsAfterInput - 1); - done() - }).catch(chaisePage.catchTestError(done)); - }); - } else { - it("zero delete buttons should be visible", function() { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - expect(buttons.length).toBe(1); - buttons.forEach(function(btn) { - expect(btn.isDisplayed()).toBe(false); - }); - }); - }); - } - }); - - describe("Submit " + tableParams.inputs.length + " records", function() { - recordEditHelpers.testSubmission(tableParams); - }); - - if (!process.env.CI && tableParams.files.length > 0) { - afterAll(function() { - recordEditHelpers.deleteFiles(tableParams.files); - console.log("\n"); - }); - } - }); - })(testParams.tables[i], i); - } - - describe('When url has a prefill query string param set, ', function() { - beforeAll(function() { - var dataRow = browser.params.entities['product-add']['category'].find(function (entity) { - return entity.id == 10007; - }); - - chaisePage.navigate(browser.params.url + "/record/#" + browser.params.catalogId + "/product-add:category/RID=" + dataRow.RID) - chaisePage.recordPageReady(); - }); - - it('should pre-fill fields from the prefill cookie', function(done) { - var addBtn = element(by.css('#rt-heading-Accommodations .add-records-link')) - var allWindows; - - chaisePage.waitForClickableElement(addBtn).then(() => { - return addBtn.click(); - }).then(() => { - return browser.getAllWindowHandles(); - }).then((handles) => { - allWindows = handles - return browser.switchTo().window(allWindows[1]); - }).then(() => { - return chaisePage.recordeditPageReady(); - }).then(() => { - var field = chaisePage.recordEditPage.getForeignKeyInputDisplay('Category', 1); - expect(field.getText()).toBe('Castle'); - - // - Go back to initial Record page - browser.close(); - return browser.switchTo().window(allWindows[0]); - }).then(() => { - - done(); - }).catch((error) => { - done.fail(error); - }); - }); - }); - - describe('Markdown Editor Help button is clicked, ', function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/product-add:accommodation"); - helpBtn = element.all(by.css('button[title=Help]')).get(0); - chaisePage.waitForElement(helpBtn); - }); - - it("should open a new window with the help page.",function(done){ - helpBtn.click().then(function () { - return browser.getAllWindowHandles(); - }).then(function(handles) { - allWindows = handles; - return browser.switchTo().window(allWindows[1]); - }).then(function() { - return chaisePage.waitForElement(element(by.id("mainTable"))); - }).then(function() { - // this is a static page, we just want to make sure this is the correct page. - // we don't need to test every element. - expect(element(by.id('mainTable')).all(by.tagName('tr')).count()).toBe(22,'Table row count could not be matched.'); - expect(element(by.id('rBold1')).getText()).toBe("**Something Bold**",'first row, first column missmatch'); - expect(element(by.id('rBold2')).getText()).toBe("__Something Bold__",'first row, second column missmatch'); - expect(element(by.id('oBold')).getAttribute('innerHTML')).toBe("Something Bold",'first row, third column missmatch'); - }).then(function() { - // - Go back to initial Record page - browser.close(); - browser.switchTo().window(allWindows[0]); - done(); - }).catch(function(error) { - done.fail(error); - }); - }); - }); -}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts index bdd0597fd..2f9d0615b 100644 --- a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts +++ b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts @@ -1,5 +1,5 @@ -import { test, expect, Page } from '@playwright/test'; -import RecordeditLocators, { RecordeditArrayBaseType, RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import { test, expect } from '@playwright/test'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; import { deleteHatracNamespaces, getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; import { createFiles, deleteFiles, testFormPresentationAndValidation, @@ -70,18 +70,18 @@ const testParams: { { name: 'opened_on', displayname: 'Operational Since', type: RecordeditInputType.TIMESTAMP, isRequired: true }, { name: 'date_col', displayname: 'date_col', type: RecordeditInputType.DATE }, { name: 'luxurious', displayname: 'Is Luxurious', type: RecordeditInputType.BOOLEAN, isRequired: true }, - { name: 'text_array', displayname: 'text_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.TEXT }, - { name: 'boolean_array', displayname: 'boolean_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.BOOLEAN }, - { name: 'int4_array', displayname: 'int4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.INT_4 }, - { name: 'float4_array', displayname: 'float4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.NUMBER }, - { name: 'date_array', displayname: 'date_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.DATE }, + { name: 'text_array', displayname: 'text_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TEXT }, + { name: 'boolean_array', displayname: 'boolean_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.BOOLEAN }, + { name: 'int4_array', displayname: 'int4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.INT_4 }, + { name: 'float4_array', displayname: 'float4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.NUMBER }, + { name: 'date_array', displayname: 'date_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.DATE }, { name: 'timestamp_array', displayname: 'timestamp_array', - type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.TIMESTAMP + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP }, { name: 'timestamptz_array', displayname: 'timestamptz_array', - type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditArrayBaseType.TIMESTAMP + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP }, { name: 'color_rgb_hex_column', displayname: 'color_rgb_hex_column', type: RecordeditInputType.COLOR, isRequired: true }, ], @@ -91,11 +91,10 @@ const testParams: { 'rating': '1', 'summary': 'This is the summary of this column 1.', 'description': '## Description 1', 'json_col': JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), 'no_of_rooms': '1', 'opened_on': { date_value: '2017-01-01', time_value: '01:01:01' }, 'date_col': '2017-01-01', 'luxurious': 'false', - // TODO - // 'text_array': ['v1', 'v2'], 'boolean_array': ['true'], - // 'int4_array': ['1', '2'], 'float4_array': ['1', '2.2'], - // 'date_array': ['2001-01-01', '2002-02-02'], 'timestamp_array': [{date_value: '2001-01-01', time_value: '01:01:01'}], - // 'timestamptz_array': [{date_value: '2001-01-01', time_value: '01:01:01'}], + 'text_array': ['v1', 'v2'], 'boolean_array': ['true'], + 'int4_array': ['1', '2'], 'float4_array': ['1', '2.2'], + 'date_array': ['2001-01-01', '2002-02-02'], 'timestamp_array': [{date_value: '2001-01-01', time_value: '01:01:01'}], + 'timestamptz_array': [{date_value: '2001-01-01', time_value: '01:01:01'}], 'color_rgb_hex_column': '#123456' }, { @@ -103,11 +102,9 @@ const testParams: { 'rating': '2', 'summary': 'This is the summary of this column 2.', 'description': '## Description 2', 'json_col': JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), 'no_of_rooms': '2', 'opened_on': { date_value: '2017-02-02', time_value: '02:02:02' }, 'date_col': '2017-02-02', 'luxurious': 'true', - // TODO - // 'text_array': ['v2', 'v3'], 'boolean_array': ['false'], - // 'int4_array': ['1', '2'], 'float4_array': ['2', '3.3'], - // 'date_array': ['2002-02-02'], 'timestamp_array': [{date_value: '2002-02-02', time_value: '02:02:02'}], - // 'timestamptz_array': [{date_value: '2002-02-02', time_value: '02:02:02'}], + 'text_array': ['v2', 'v3'], 'boolean_array': ['false'], 'int4_array': ['1', '2'], 'float4_array': ['2', '3.3'], + 'date_array': ['2002-02-02'], 'timestamp_array': [{date_value: '2002-02-02', time_value: '02:02:02'}], + 'timestamptz_array': [{date_value: '2002-02-02', time_value: '02:02:02'}], 'color_rgb_hex_column': '#654321' } ] @@ -126,8 +123,7 @@ const testParams: { '1.0000', 'This is the summary of this column 1.', 'Description 1', JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), '1', '2017-01-01 01:01:01', '2017-01-01', 'false', - '', '', '', '', '', '', '', // TODO - // 'v1, v2', 'true', '1, 2', '1.0000, 2.2000', '2001-01-01, 2002-02-02', '2001-01-01 01:01:01', '2001-01-01 01:01:01', + 'v1, v2', 'true', '1, 2', '1.0000, 2.2000', '2001-01-01, 2002-02-02', '2001-01-01 01:01:01', '2001-01-01 01:01:01', '#123456' ], [ @@ -136,14 +132,12 @@ const testParams: { '2.0000', 'This is the summary of this column 2.', 'Description 2', JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), '2', '2017-02-02 02:02:02', '2017-02-02', 'true', - '', '', '', '', '', '', '', // TODO - // 'v2, v3', 'false', '1, 2', '2.0000, 3.3000', '2002-02-02', '2002-02-02 02:02:02', '2002-02-02 02:02:02', + 'v2, v3', 'false', '1, 2', '2.0000, 3.3000', '2002-02-02', '2002-02-02 02:02:02', '2002-02-02 02:02:02', '#654321' ] ] } }, - // TODO { num_files: 2, // only two forms will be submitted presentation: { @@ -271,17 +265,28 @@ test.describe('Recordedit create', () => { const presentation = params.presentation; test(`${presentation.description}`, async ({ page, baseURL }, testInfo) => { + const numForms = presentation.inputs.length; + await test.step('open recordedit page', async () => { const url = `${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/${presentation.schemaName}:${presentation.tableName}`; await page.goto(url); }); + if (numForms > 1) { + await test.step('clone more forms', async () => { + let cnt = 1; + while (cnt < numForms) { + await RecordeditLocators.getCloneFormInputSubmitButton(page).click(); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(++cnt); + } + }); + } + // test everything related to the form await testFormPresentationAndValidation(page, baseURL, testInfo, presentation, false); // this test clones the form and then remove the extra one. just to make sure nothing is affected by this. await test.step('remove form', async () => { - const numForms = presentation.inputs.length; // for the tests with file, we want to remove the actual form instead of adding a new one. const addExtraForm = params.num_files === 0; @@ -317,10 +322,6 @@ test.describe('Recordedit create', () => { }); } - // TODO When url has a prefill query string param set - - - test.afterAll(async () => { await deleteFiles(testFiles); await deleteHatracNamespaces([`/hatrac/js/chaise/${currentTimestampTimeStr}`]); diff --git a/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts b/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts index 7264235b2..fa75d420f 100644 --- a/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts +++ b/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts @@ -1,5 +1,5 @@ import { test, expect, Page } from '@playwright/test'; -import RecordeditLocators, { RecordeditArrayBaseType, RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; import { getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; import { createFiles, deleteFiles, setInputValue, testFormValuesForAColumn, testSubmission @@ -456,7 +456,7 @@ const testParams = { }, { type: RecordeditInputType.ARRAY, - arrayBaseType: RecordeditArrayBaseType.TEXT, + arrayBaseType: RecordeditInputType.TEXT, column_name: 'array_text', column_displayname: 'array_text', apply_to_all: { diff --git a/test/e2e/utils/recordedit-utils.ts b/test/e2e/utils/recordedit-utils.ts index 93e947d50..4332ed2b2 100644 --- a/test/e2e/utils/recordedit-utils.ts +++ b/test/e2e/utils/recordedit-utils.ts @@ -1,15 +1,20 @@ +import test, { expect, Locator, Page, TestInfo } from '@playwright/test'; import { execSync } from 'child_process'; import { resolve } from 'path'; -import test, { expect, Locator, Page, TestInfo } from '@playwright/test'; +import moment from 'moment'; -import { APP_NAMES, UPLOAD_FOLDER } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; -import RecordeditLocators, { RecordeditArrayBaseType, RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +// locators +import AlertLocators from '@isrd-isi-edu/chaise/test/e2e/locators/alert'; import ModalLocators from '@isrd-isi-edu/chaise/test/e2e/locators/modal'; +import PageLocators from '@isrd-isi-edu/chaise/test/e2e/locators/page'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset'; + +// utils +import { APP_NAMES, UPLOAD_FOLDER } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; import { RecordsetRowValue, testRecordsetTableRowValues } from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils'; -import AlertLocators from '@isrd-isi-edu/chaise/test/e2e/locators/alert'; import { testRecordMainSectionValues } from '@isrd-isi-edu/chaise/test/e2e/utils/record-utils'; -import { testTooltip } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils'; +import { clickNewTabLink, testTooltip } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils'; import { getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; export type RecordeditExpectedColumn = { @@ -61,7 +66,7 @@ export const createFiles = async (files: RecordeditFile[]) => { if (!f.skipCreation) { const path = resolve(UPLOAD_FOLDER, f.path); execSync(`mkdir -p ${UPLOAD_FOLDER}`); - execSync(`perl -e 'print \"1\" x ${f.size}' > ${path}`); + execSync(`perl -e 'print \'1\' x ${f.size}' > ${path}`); console.log(`${path} created`); } } @@ -150,7 +155,7 @@ type SetInputValueProps = string | RecordeditFile | { */ export const setInputValue = async ( page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, - valueProps: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditArrayBaseType + valueProps: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditInputType ) => { switch (inputType) { case RecordeditInputType.BOOLEAN: @@ -228,7 +233,7 @@ export const setInputValue = async ( case RecordeditInputType.ARRAY: if (!Array.isArray(valueProps) || arrayBaseType === undefined) return; - const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber, 'text'); + const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber); // remove the existing value if there are any while (await elems.removeItemButtons.count() > 0) { @@ -236,29 +241,16 @@ export const setInputValue = async ( } // add the values one by one. - // TODO finalize this for (const val of valueProps) { - switch (arrayBaseType) { - case RecordeditArrayBaseType.TIMESTAMP: - if (typeof val !== 'object' || !(('time_value' in val) && ('date_value' in val))) { - return; - } - const inputs = RecordeditLocators.getTimestampInputsForAColumn(page, name, formNumber); - await inputs.clearBtn.click(); - await inputs.date.clear(); - await inputs.date.fill(val.date_value); - await inputs.time.clear(); - await inputs.time.fill(val.time_value); - - break; - case RecordeditArrayBaseType.BOOLEAN: - break; - default: - if (typeof val !== 'string') continue; - await elems.addItemInput.fill(val); - await elems.addItemButton.click(); - break; - } + const addItemName = RecordeditLocators.getArrayInputName(name, -1); + const addOrDiscardMessage = 'Click \'Add\' to include the value or clear the entry to discard.'; + const addItemError = RecordeditLocators.getErrorMessageForAColumn(page, addItemName, formNumber); + + await setInputValue(page, formNumber, addItemName, displayname, arrayBaseType, val); + // TODO timestamp has a bug where the error doesn't show up. + // await expect.soft(addItemError).toBeVisible(); + // await expect.soft(addItemError).toHaveText(addOrDiscardMessage); + await elems.addItemButton.click(); } break; @@ -278,7 +270,7 @@ export const setInputValue = async ( */ export const testInputValue = async ( page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, - disabled: boolean, valueProps?: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditArrayBaseType + disabled: boolean, valueProps?: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditInputType ) => { let input; const inputControl = RecordeditLocators.getInputControlForAColumn(page, name, formNumber); @@ -342,21 +334,12 @@ export const testInputValue = async ( break; case RecordeditInputType.ARRAY: - // TODO support more types - if (!Array.isArray(valueProps)) return; - const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber, 'text'); + if (!Array.isArray(valueProps) || arrayBaseType === undefined) return; - let index = 0; - for (const val of valueProps) { - if (typeof val !== 'string') continue; - input = elems.inputs.nth(index); - if (disabled) { - await expect.soft(input).toBeDisabled(); - } - await expect.soft(input).toHaveValue(val); - index++; + for (const [index, val] of valueProps.entries()) { + const itemName = RecordeditLocators.getArrayInputName(name, index); + await testInputValue(page, formNumber, itemName, displayname, arrayBaseType, disabled, val); } - break; default: @@ -384,7 +367,7 @@ export const testInputValue = async ( * * @param name the column name * @param displayname the column displayname - * @param displayType the display type (boolean, fk, timestamp, upload, "any other string") + * @param displayType the display type (boolean, fk, timestamp, upload, 'any other string') * @param allDisabled whether we should test that all the inputs are disabled or not * @param expectedValues the expected values * @returns @@ -451,7 +434,7 @@ export type TestFormPresentationAndValidation = { type: RecordeditInputType, - arrayBaseType?: RecordeditArrayBaseType, + arrayBaseType?: RecordeditInputType, isRequired?: boolean, comment?: string, inlineComment?: string, @@ -496,9 +479,13 @@ export const testFormPresentationAndValidation = async ( let pageTitle; if (isEditMode) { - pageTitle = `Edit ${params.tableDisplayname}: ${params.rowName}`; + if (params.rowName) { + pageTitle = `Edit ${params.tableDisplayname}: ${params.rowName}`; + } else { + pageTitle = `Edit ${params.inputs.length} ${params.tableDisplayname} records`; + } } else { - pageTitle = `Create 1 ${params.tableDisplayname} record`; + pageTitle = `Create ${params.inputs.length} ${params.tableDisplayname} ${params.inputs.length > 1 ? 'records' : 'record'}`; } await expect.soft(RecordeditLocators.getPageTitle(page)).toHaveText(pageTitle); @@ -576,43 +563,426 @@ export const testFormPresentationAndValidation = async ( }); for await (const recordIndex of Array.from(Array(params.inputs.length).keys())) { - - if (recordIndex > 0) { - await test.step('should be able to clone new record', async () => { - await RecordeditLocators.getCloneFormInputSubmitButton(page).click(); - await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(recordIndex + 1); - }); - } + const formNumber = recordIndex + 1; for await (const col of params.columns) { - await test.step(`record index=${recordIndex}, column ${col.name}, `, async () => { + await test.step(`record ${formNumber}, column ${col.name} (${col.type}${col.arrayBaseType ? ' ' + col.arrayBaseType : ''}), `, async () => { + const cellError = RecordeditLocators.getErrorMessageForAColumn(page, col.name, formNumber); + const existingValue = _getColumnValue(recordIndex, col.name); // check the input and value await test.step('show the input with the correct value', async () => { - await testInputValue(page, recordIndex + 1, col.name, col.displayname, col.type, !!col.disabled, _getColumnValue(recordIndex, col.name)); + await testInputValue(page, formNumber, col.name, col.displayname, col.type, !!col.disabled, existingValue); }); if (col.disabled) return; - // TODO // test the validators and extra features if needed - // if (!col.skipValidation) { - // switch (col.type) { - // case RecordeditInputType.ARRAY: - // // TODO - // break; - - // default: - // break; - // } - // } + if (!col.skipValidation) { + switch (col.type) { + case RecordeditInputType.ARRAY: + // TODO + break; + + case RecordeditInputType.JSON: + case RecordeditInputType.JSONB: + const jsonInput = RecordeditLocators.getInputForAColumn(page, col.name, formNumber); + + await test.step('should allow any valid JSON values.', async () => { + const validJSONValues = [ + { stringVal: '{}', description: 'empty object' }, + { stringVal: '{\"name\":\"tester\"}', description: 'object' }, + { stringVal: '6534.9987', description: 'number' }, + { stringVal: 'null', description: 'null' }, + { stringVal: '\" \"', description: 'string of spaces' } + ]; + + for await (const val of validJSONValues) { + await jsonInput.clear(); + await jsonInput.fill(val.stringVal); + await expect.soft(jsonInput, val.description).toHaveValue(val.stringVal); + await expect.soft(cellError, val.description).not.toBeAttached(); + await jsonInput.clear(); + } + }); + + await test.step('should not allow invalid JSON values', async () => { + const invalidJSONValues = [ + { stringVal: '{', description: 'only {' }, + { stringVal: '{name\":\"tester\"}', description: 'missing double qoute' }, + { stringVal: ' ', description: 'empty' } + ]; + + for await (const val of invalidJSONValues) { + await jsonInput.clear(); + await jsonInput.fill(val.stringVal); + await expect.soft(jsonInput, val.description).toHaveValue(val.stringVal); + await expect.soft(cellError, val.description).toBeVisible(); + await expect.soft(cellError, val.description).toHaveText('Please enter a valid JSON value.'); + await jsonInput.clear(); + } + }); + break; + + case RecordeditInputType.MARKDOWN: + const markdownProps = RecordeditLocators.getMarkdownElements(page, col.name, formNumber); + + await test.step('should render markdown with inline preview and full preview button.', async () => { + const markdownTestParams = [{ + input: 'RBK Project ghriwvfw nwoeifwiw qb2372b wuefiquhf pahele kabhi na phelke kabhiy gqeequhwqh', + html: '

RBK Project ghriwvfw nwoeifwiw qb2372b wuefiquhf pahele kabhi na phelke kabhiy gqeequhwqh

\n', + title: 'Heading' + }, { + input: 'E15.5 embryonic kidneys for sections\n' + + '- E18.5 embryonic kidneys for cDNA synthesis\n' + + '- Sterile PBS\n' + + '- QIAShredder columns (Qiagen, cat no. 79654)\n' + + '- DEPC-Treated Water', + html: '\n', + title: 'Unordered List' + }, { + // eslint-disable-next-line max-len + input: 'This is bold text. nuf2uh3498hcuh23uhcu29hh nfwnfi2nfn k2mr2ijri. Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru', + // eslint-disable-next-line max-len + html: '

This is bold text. nuf2uh3498hcuh23uhcu29hh nfwnfi2nfn k2mr2ijri. Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru

\n', + title: 'Bold' + }, { + // eslint-disable-next-line max-len + input: 'This is italic text fcj2ij3ijjcn 2i3j2ijc3roi2joicj. Hum ja rahal chi gaam ta pher kail aaib. Khana kha ka aib rehal chi parson tak.', + // eslint-disable-next-line max-len + html: '

This is italic text fcj2ij3ijjcn 2i3j2ijc3roi2joicj. Hum ja rahal chi gaam ta pher kail aaib. Khana kha ka aib rehal chi parson tak.

\n', + title: 'Italic' + }, { + input: '~~Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru~~', + html: '

Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru

\n', + title: '' + }, { + input: 'X^2^+Y^2^+Z^2^=0', + html: '

X2+Y2+Z2=0

\n', + title: '' + }, { + input: '[[RID]]', + html: '

RID

\n', + title: '' + }]; + + const mdInput = RecordeditLocators.getInputForAColumn(page, col.name, formNumber); + for await (const param of markdownTestParams) { + //if title defined found for markdown elements then send click command + if (param.title) { + const mdOption = markdownProps.getButton(param.title); + await expect.soft(mdOption).toBeVisible(); + await mdOption.click(); + } + + // .fill will replace the content but we want to append to the content, so using .pressSequentially instead + await mdInput.pressSequentially(param.input); + + // inline preview + await markdownProps.previewButton.click(); + await expect.soft(markdownProps.previewContent).toBeVisible(); + expect.soft(await markdownProps.previewContent.innerHTML()).toEqual(param.html); + await markdownProps.previewButton.click(); + await expect.soft(markdownProps.previewContent).not.toBeAttached(); + + // modal preview + const modal = ModalLocators.getMarkdownPreviewModal(page); + await markdownProps.fullScreenButton.click(); + await expect.soft(modal).toBeVisible(); + const modalContent = ModalLocators.getMarkdownPreviewContent(modal); + await expect.soft(modalContent).toBeVisible(); + expect.soft(await modalContent.innerHTML()).toEqual(param.html); + + await ModalLocators.getCloseBtn(modal).click(); + await expect.soft(modal).not.toBeAttached(); + + await mdInput.clear(); + } + }); + + await test.step('help button should open the help page', async () => { + const newPage = await clickNewTabLink(markdownProps.helpButton); + await newPage.waitForURL('**/help/?page=chaise%2Fmarkdown-help'); + await expect.soft(PageLocators.getHelpPageMainTable(newPage)).toBeVisible(); + await newPage.close(); + }); + break; + + case RecordeditInputType.FK_POPUP: + const displayedValue = RecordeditLocators.getForeignKeyInputDisplay(page, col.displayname, formNumber); + + if (typeof existingValue === 'string') { + await test.step('clicking the "x" should remove the value in the foreign key field.', async () => { + await expect.soft(displayedValue).toHaveText(existingValue); + await RecordeditLocators.getForeignKeyInputClear(page, col.displayname, formNumber).click(); + await expect.soft(displayedValue).toHaveText('Select a value'); + }); + } + + await test.step('popup selector', async () => { + const rsModal = ModalLocators.getForeignKeyPopup(page); + await test.step('should have the proper title.', async () => { + await RecordeditLocators.getForeignKeyInputButton(page, col.displayname, formNumber).click(); + await expect.soft(rsModal).toBeVisible(); + await expect.soft(RecordsetLocators.getRows(rsModal)).not.toHaveCount(0); + let title = `Select ${col.displayname} for `; + if (isEditMode) { + title += `${params.tableDisplayname}: ${params.rowName}`; + } else { + title += `new ${params.tableDisplayname}`; + } + await expect.soft(ModalLocators.getModalTitle(rsModal)).toHaveText(title); + }); + + await test.step('closing without selecting should not select any values.', async () => { + await ModalLocators.getCloseBtn(rsModal).click(); + await expect.soft(rsModal).not.toBeAttached(); + await expect.soft(displayedValue).toHaveText('Select a value'); + }); + }); + break; + + case RecordeditInputType.DATE: + const dateInputProps = RecordeditLocators.getDateInputsForAColumn(page, col.name, formNumber); + const dateRemoveBtn = RecordeditLocators.getInputRemoveButton(page, col.name, formNumber); + + await test.step('should complain about invalid date values.', async () => { + // testing partial input + await dateInputProps.date.clear(); + await dateInputProps.date.fill('1234-1'); + await expect.soft(cellError).toHaveText('Please enter a valid date value in YYYY-MM-DD format.'); + + // clear the input and see if the error disapears + // (.clear() wasn't working consistently) + await dateRemoveBtn.click(); + await expect.soft(cellError).not.toBeAttached(); + }); + + await test.step('"Today" button should enter the current date into the input', async () => { + await dateInputProps.todayBtn.click(); + await expect.soft(dateInputProps.date).toHaveValue(moment().format('YYYY-MM-DD')); + }); + + await test.step('"clear" button should clear the date.', async () => { + await expect.soft(dateRemoveBtn).toBeVisible(); + await dateRemoveBtn.click(); + await expect.soft(dateInputProps.date).toHaveValue(''); + await expect.soft(dateRemoveBtn).not.toBeAttached(); + }); + + break; + + case RecordeditInputType.TIMESTAMP: + const timestampProps = RecordeditLocators.getTimestampInputsForAColumn(page, col.name, formNumber); + const timeErrorMessage = 'Please enter a valid time value in 24-hr HH:MM:SS format.'; + const dateErrorMessage = 'Please enter a valid date value in YYYY-MM-DD format.'; + + await test.step('should complain about invalid values and allow valid ones.', async () => { + await timestampProps.date.clear(); + await timestampProps.time.clear(); + + // the test cases below are only checking time, so we should first add a proper date. + await timestampProps.date.fill('2016-01-01'); + + // If user enters an invalid time an error msg should appear + await timestampProps.time.fill('24:12:00'); + await expect.soft(cellError).toHaveText(timeErrorMessage); + + // If user enters a valid time, then error msg should disappear + await timestampProps.time.fill('12:00:00'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter 1 digit in any place + await timestampProps.time.clear(); + await timestampProps.time.fill('2:2:2'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter just the hours + await timestampProps.time.clear(); + await timestampProps.time.fill('08'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter just the hours and minutes + await timestampProps.time.clear(); + await timestampProps.time.fill('2:10'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter 0 for the time + await timestampProps.time.clear(); + await timestampProps.time.fill('00:00:00'); + await expect.soft(cellError).not.toBeAttached(); + + // Invalid date + good time = error + // If user enters a valid time but no date, an error msg should appear + await timestampProps.date.clear(); + await timestampProps.time.clear(); + await timestampProps.time.fill('12:00:00'); + await expect.soft(cellError).toHaveText(dateErrorMessage); + + // Good date + good time = no error + // Now, if user enters a valid date, then no error message should appear + await timestampProps.date.fill('2016-01-01'); + await expect.soft(cellError).not.toBeAttached(); + + // Good date + clear time = no error + await timestampProps.time.clear(); + await expect.soft(cellError).not.toBeAttached(); + + }); + + await test.step('"clear" button should clear both time and date.', async () => { + await timestampProps.clearBtn.click(); + await expect.soft(timestampProps.date).toHaveValue(''); + await expect.soft(timestampProps.time).toHaveValue(''); + }); + + await test.step('"Now" button should enter the current date and time', async () => { + const nowObject = moment(); + const nowDate = nowObject.format('YYYY-MM-DD'); + await timestampProps.nowBtn.click(); + await expect.soft(timestampProps.date).toHaveValue(nowDate); + + await expect.soft(timestampProps.time).not.toHaveValue(''); + const UITime = await timestampProps.time.getAttribute('value') as string; + const UIObject = moment(nowDate + UITime, 'YYYY-MM-DDhh:mm'); + expect.soft(UIObject.diff(nowObject, 'minutes')).toEqual(0); + }); + + break; + + case RecordeditInputType.INT_2: + case RecordeditInputType.INT_4: + case RecordeditInputType.INT_8: + const intInput = RecordeditLocators.getInputForAColumn(page, col.name, formNumber); + + await test.step('should complain about invalid values and allow valid ones.', async () => { + // a non-number value + await intInput.clear(); + await intInput.fill('1j2yu'); + await expect.soft(cellError).toHaveText('Please enter a valid integer value.'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + // float number + await intInput.fill('12.1'); + await expect.soft(cellError).toHaveText('Please enter a valid integer value.'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + // min and max values + let invalidMaxNo = '2343243243242414423243242353253253253252352', invalidMinNo = '-2343243243242414423243242353253253253252352'; + if (col.type === RecordeditInputType.INT_2) { + invalidMaxNo = '8375832757832', invalidMinNo = '-237587565'; + } else if (col.type === RecordeditInputType.INT_4) { + invalidMaxNo = '3827374576453', invalidMinNo = '-326745374576375'; + } + + await intInput.clear(); + await intInput.fill(invalidMaxNo); + await expect.soft(cellError).toContainText('This field requires a value less than'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + await intInput.fill(invalidMinNo); + await expect.soft(cellError).toContainText('This field requires a value greater than'); + + await intInput.clear(); + await intInput.fill('12') + await expect.soft(cellError).not.toBeAttached(); + + await intInput.clear(); + }); + break; + + case RecordeditInputType.NUMBER: + const numInput = RecordeditLocators.getInputForAColumn(page, col.name, formNumber); + + await test.step('should complain about invalid values and allow valid ones.', async () => { + // a non-number value + await numInput.clear(); + await numInput.fill('1j2yu'); + await expect.soft(cellError).toHaveText('Please enter a valid decimal value.'); + + // float number + await numInput.clear(); + await numInput.fill('12.1'); + await expect.soft(cellError).not.toBeAttached(); + + // int number + await numInput.clear(); + await numInput.fill('12') + await expect.soft(cellError).not.toBeAttached(); + + await numInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + }); + break; + + case RecordeditInputType.COLOR: + const colorInput = RecordeditLocators.getInputForAColumn(page, col.name, formNumber); + + await test.step('should now allow invalid values and allow valid ones.', async () => { + // an incomplete value + await colorInput.clear(); + await colorInput.fill('#de'); + // the input won't validate until we focus somewhere else + await RecordeditLocators.getRequiredInfoEl(page).click(); + await expect.soft(colorInput).toHaveValue('#'); + + // a valid color + await colorInput.clear(); + await colorInput.fill('#abcabc'); + await expect.soft(colorInput).toHaveValue('#abcabc'); + await expect.soft(cellError).not.toBeAttached(); + }); + + await test.step('color picker popup', async () => { + // focus somewhere else to make sure the popup is still not open + await RecordeditLocators.getRequiredInfoEl(page).click(); + // open the popup + await RecordeditLocators.getColorInputBtn(page, col.name, formNumber).click(); + + const colorPopup = RecordeditLocators.getColorInputPopup(page); + await expect.soft(colorPopup).toBeVisible(); + + // make sure clear btn is offered regardless of null/not-null (just like any other input) + const colorClearBtn = RecordeditLocators.getColorInputPopupClearBtn(page); + await expect.soft(colorClearBtn).toBeVisible(); + + const colorPopupInput = RecordeditLocators.getColorInputPopupInput(page); + await expect.soft(colorPopupInput).toBeVisible(); + + // make sure users can use the color popup input + const colorVal = '#555555'; + await colorPopupInput.clear(); + await colorPopupInput.fill(colorVal); + await RecordeditLocators.getColorInputPopupSelectBtn(page).click(); + await expect.soft(colorPopup).not.toBeAttached(); + + await expect.soft(colorInput).toHaveValue(colorVal); + expect.soft(await RecordeditLocators.getColorInputBackground(page, col.name, formNumber)).toEqual(colorVal); + + }); + + break; + } + } // set the value const newVal = _getColumnInput(recordIndex, col.name); if (newVal === undefined) return; await test.step('set the new value', async () => { - await setInputValue(page, recordIndex + 1, col.name, col.displayname, col.type, newVal, col.arrayBaseType); + await setInputValue(page, formNumber, col.name, col.displayname, col.type, newVal, col.arrayBaseType); }); });