diff --git a/e2e/process-services-cloud/task-list/edit-task-filters-component.e2e.ts b/e2e/process-services-cloud/task-list/edit-task-filters-component.e2e.ts index a4eb1b20a6..b90d229c15 100644 --- a/e2e/process-services-cloud/task-list/edit-task-filters-component.e2e.ts +++ b/e2e/process-services-cloud/task-list/edit-task-filters-component.e2e.ts @@ -22,7 +22,8 @@ import { LoginPage, TasksService, IdentityService, - GroupIdentityService + GroupIdentityService, + EditTaskFilterDialogPage } from '@alfresco/adf-testing'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; import { TasksCloudDemoPage } from './../pages/tasks-cloud-demo.page'; @@ -38,6 +39,7 @@ describe('Edit task filters cloud', () => { const tasksCloudDemoPage = new TasksCloudDemoPage(); const editTaskFilter = tasksCloudDemoPage.editTaskFilterCloud; const taskFilter = tasksCloudDemoPage.taskFilterCloudComponent; + const editTaskFilterDialog = new EditTaskFilterDialogPage(); const apiService = createApiService(); const identityService = new IdentityService(apiService); @@ -49,10 +51,18 @@ describe('Edit task filters cloud', () => { const completedTaskName = StringUtil.generateRandomString(); const assignedTaskName = StringUtil.generateRandomString(); + /** + * Click on the specified task filter + * + * @param name filter name + */ async function clickTaskFilter(name: string) { await taskFilter.clickTaskFilter(name); } + /** + * Wait till the datatable component is loaded + */ async function waitTillContentLoaded() { await tasksCloudDemoPage.taskListCloudComponent().getDataTable().waitTillContentLoaded(); } @@ -187,7 +197,6 @@ describe('Edit task filters cloud', () => { await editTaskFilter.clickSaveAsButton(); - const editTaskFilterDialog = editTaskFilter.editTaskFilterDialog(); await editTaskFilterDialog.setFilterName('New'); await editTaskFilterDialog.clickOnSaveButton(); @@ -197,8 +206,8 @@ describe('Edit task filters cloud', () => { await expect(await editTaskFilter.getSortFilterDropDownValue()).toEqual('id'); await editTaskFilter.setSortFilterDropDown('priority'); await editTaskFilter.clickSaveAsButton(); - await editTaskFilter.editTaskFilterDialog().setFilterName('New'); - await editTaskFilter.editTaskFilterDialog().clickOnSaveButton(); + await editTaskFilterDialog.setFilterName('New'); + await editTaskFilterDialog.clickOnSaveButton(); await expect(await taskFilter.getActiveFilterName()).toBe('New'); await editTaskFilter.openFilter(); @@ -224,7 +233,6 @@ describe('Edit task filters cloud', () => { await editTaskFilter.clickSaveAsButton(); - const editTaskFilterDialog = await editTaskFilter.editTaskFilterDialog(); await editTaskFilterDialog.setFilterName('New'); await editTaskFilterDialog.clickOnSaveButton(); @@ -252,7 +260,6 @@ describe('Edit task filters cloud', () => { await editTaskFilter.clickSaveAsButton(); - const editTaskFilterDialog = await editTaskFilter.editTaskFilterDialog(); await editTaskFilterDialog.setFilterName('New'); await editTaskFilterDialog.clickOnSaveButton(); @@ -275,12 +282,10 @@ describe('Edit task filters cloud', () => { await expect(await editTaskFilter.getSortFilterDropDownValue()).toEqual('priority'); await editTaskFilter.clickSaveAsButton(); - const dialog = editTaskFilter.editTaskFilterDialog(); - - await expect(await dialog.getFilterName()).toEqual('My Tasks'); - await dialog.setFilterName('Cancel'); - await expect(await dialog.getFilterName()).toEqual('Cancel'); - await dialog.clickOnCancelButton(); + await expect(await editTaskFilterDialog.getFilterName()).toEqual('My Tasks'); + await editTaskFilterDialog.setFilterName('Cancel'); + await expect(await editTaskFilterDialog.getFilterName()).toEqual('Cancel'); + await editTaskFilterDialog.clickOnCancelButton(); await taskFilter.checkTaskFilterNotDisplayed('Cancel'); await expect(await taskFilter.getActiveFilterName()).toEqual('My Tasks'); @@ -307,14 +312,12 @@ describe('Edit task filters cloud', () => { await expect(await editTaskFilter.getSortFilterDropDownValue()).toEqual('id'); await editTaskFilter.clickSaveAsButton(); - const dialog = editTaskFilter.editTaskFilterDialog(); - - await expect(await dialog.getFilterName()).toEqual('My Tasks'); - await dialog.clearFilterName(); - await expect(await dialog.getFilterName()).toEqual(''); - await expect(await dialog.checkSaveButtonIsEnabled()).toEqual(false); - await expect(await dialog.checkCancelButtonIsEnabled()).toEqual(true); - await dialog.clickOnCancelButton(); + await expect(await editTaskFilterDialog.getFilterName()).toEqual('My Tasks'); + await editTaskFilterDialog.clearFilterName(); + await expect(await editTaskFilterDialog.getFilterName()).toEqual(''); + await expect(await editTaskFilterDialog.checkSaveButtonIsEnabled()).toEqual(false); + await expect(await editTaskFilterDialog.checkCancelButtonIsEnabled()).toEqual(true); + await editTaskFilterDialog.clickOnCancelButton(); }); it('[C291799] Task filter dialog is displayed when clicking on Save As button', async () => { @@ -326,15 +329,18 @@ describe('Edit task filters cloud', () => { await expect(await editTaskFilter.getSortFilterDropDownValue()).toEqual('id'); await editTaskFilter.clickSaveAsButton(); - const dialog = editTaskFilter.editTaskFilterDialog(); - - await expect(await dialog.checkSaveButtonIsEnabled()).toEqual(true); - await expect(await dialog.checkCancelButtonIsEnabled()).toEqual(true); - await expect(await dialog.getTitle()).toEqual('Save filter as'); - await expect(await dialog.getFilterName()).toEqual('My Tasks'); - await dialog.clickOnCancelButton(); + await expect(await editTaskFilterDialog.checkSaveButtonIsEnabled()).toEqual(true); + await expect(await editTaskFilterDialog.checkCancelButtonIsEnabled()).toEqual(true); + await expect(await editTaskFilterDialog.getTitle()).toEqual('Save filter as'); + await expect(await editTaskFilterDialog.getFilterName()).toEqual('My Tasks'); + await editTaskFilterDialog.clickOnCancelButton(); }); + /** + * Creates new custom filter + * + * @param name Filter name + */ async function createNewCustomFilter(name: string): Promise { await clickTaskFilter('my-tasks'); await waitTillContentLoaded(); @@ -347,8 +353,7 @@ describe('Edit task filters cloud', () => { await editTaskFilter.clickSaveAsButton(); - const dialog = editTaskFilter.editTaskFilterDialog(); - await dialog.setFilterName(name); - await dialog.clickOnSaveButton(); + await editTaskFilterDialog.setFilterName(name); + await editTaskFilterDialog.clickOnSaveButton(); } }); diff --git a/e2e/process-services/form/dynamic-table-date-picker.e2e.ts b/e2e/process-services/form/dynamic-table-date-picker.e2e.ts index 9ac1b99a4c..a13cf56c46 100644 --- a/e2e/process-services/form/dynamic-table-date-picker.e2e.ts +++ b/e2e/process-services/form/dynamic-table-date-picker.e2e.ts @@ -15,17 +15,7 @@ * limitations under the License. */ -import { - createApiService, - ApplicationsUtil, - DatePickerCalendarPage, - DateUtil, - LoginPage, - ModelsActions, - UsersActions, - Widget, - UserModel -} from '@alfresco/adf-testing'; +import { createApiService, ApplicationsUtil, LoginPage, ModelsActions, UsersActions, Widget, UserModel } from '@alfresco/adf-testing'; import { ProcessFiltersPage } from '../pages/process-filters.page'; import { ProcessServiceTabBarPage } from '../pages/process-service-tab-bar.page'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; @@ -35,7 +25,6 @@ describe('Dynamic Table', () => { const loginPage = new LoginPage(); const processFiltersPage = new ProcessFiltersPage(); const processServiceTabBarPage = new ProcessServiceTabBarPage(); - const datePicker = new DatePickerCalendarPage(); const navigationBarPage = new NavigationBarPage(); const widget = new Widget(); @@ -58,78 +47,6 @@ describe('Dynamic Table', () => { await usersActions.deleteTenant(tenantId); }); - describe('Date Picker', () => { - const app = browser.params.resources.Files.DYNAMIC_TABLE_APP; - - const randomText = { - date: '12/12/2012', - wrongDate: 'HELLO WORLD', - wrongDateTime: 'Test', - dateTime: '15/07/2019 23:55', - error: `Invalid 'columnDate' format.`, - requiredError: `Field 'columnDate' is required.` - }; - const currentDate = DateUtil.formatDate('DD-MM-YYYY'); - const rowPosition = 0; - - beforeAll(async () => { - await apiService.login(user.username, user.password); - const applicationsService = new ApplicationsUtil(apiService); - const importedApp = await applicationsService.importPublishDeployApp(app.file_path); - appId = importedApp.id; - await loginPage.login(user.username, user.password); - }); - - afterAll(async () => { - await apiService.login(user.username, user.password); - await modelsActions.deleteModel(appId); - await navigationBarPage.clickLogoutButton(); - }); - - beforeEach(async () => { - await (await (await navigationBarPage.navigateToProcessServicesPage()).goToTaskApp()).clickProcessButton(); - await processServiceTabBarPage.clickProcessButton(); - - await processFiltersPage.clickCreateProcessButton(); - await processFiltersPage.clickNewProcessDropdown(); - }); - - it('[C286277] Should have a datepicker and a mask for DateTime field', async () => { - await widget.dynamicTable().clickAddRow(); - await widget.dynamicTable().clickColumnDateTime(); - - await expect(await widget.dynamicTable().addRandomStringOnDateTime(randomText.wrongDateTime)).toBe(''); - }); - - it('[C286279] Should be able to save row with Date field', async () => { - await widget.dynamicTable().clickAddRow(); - await widget.dynamicTable().addRandomStringOnDate(randomText.wrongDate); - await widget.dynamicTable().clickSaveButton(); - await expect(await widget.dynamicTable().checkErrorMessage()).toBe(randomText.error); - - await widget.dynamicTable().clickDateWidget(); - await datePicker.selectTodayDate(); - await datePicker.checkDatePickerIsNotDisplayed(); - await widget.dynamicTable().clickSaveButton(); - await widget.dynamicTable().getTableRow(rowPosition); - await expect(await widget.dynamicTable().getTableCellText(rowPosition, 1)).toBe(currentDate); - }); - - it('[C311456] Should be able to delete date that is not mandatory and save the Dynamic Table', async () => { - await widget.dynamicTable().clickAddRow(); - await widget.dynamicTable().clickSaveButton(); - await expect(await widget.dynamicTable().checkErrorMessage()).toBe(randomText.requiredError); - - await widget.dynamicTable().clickDateWidget(); - await datePicker.selectTodayDate(); - await datePicker.checkDatePickerIsNotDisplayed(); - await widget.dynamicTable().clickSaveButton(); - await widget.dynamicTable().getTableRow(rowPosition); - await expect(await widget.dynamicTable().getTableCellText(rowPosition, 1)).toBe(currentDate); - await expect(await widget.dynamicTable().getTableCellText(rowPosition, 2)).toBe(''); - }); - }); - describe('Required Dropdown', () => { const app = browser.params.resources.Files.APP_DYNAMIC_TABLE_DROPDOWN; const dropdown = widget.dropdown(); diff --git a/e2e/process-services/widgets/date-time-widget.e2e.ts b/e2e/process-services/widgets/date-time-widget.e2e.ts deleted file mode 100644 index fd40899a8d..0000000000 --- a/e2e/process-services/widgets/date-time-widget.e2e.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * @license - * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createApiService, - ApplicationsUtil, - LoginPage, - ProcessUtil, - UsersActions, - Widget, UserModel -} from '@alfresco/adf-testing'; -import { TasksPage } from '../pages/tasks.page'; -import { browser } from 'protractor'; -import CONSTANTS = require('../../util/constants'); -import { ProcessServicesPage } from '../pages/process-services.page'; -import { AppDefinitionRepresentation, ProcessInstanceRepresentation } from '@alfresco/js-api'; - -describe('Date and time widget', () => { - - const app = browser.params.resources.Files.WIDGET_CHECK_APP.DATETIME; - - const loginPage = new LoginPage(); - const taskPage = new TasksPage(); - const widget = new Widget(); - - const apiService = createApiService(); - const usersActions = new UsersActions(apiService); - const applicationsService = new ApplicationsUtil(apiService); - const processUtil = new ProcessUtil(apiService); - - let processUserModel: UserModel; - let appModel: AppDefinitionRepresentation; - let deployedAppId: number; - let process: ProcessInstanceRepresentation; - - beforeAll(async () => { - await apiService.loginWithProfile('admin'); - - processUserModel = await usersActions.createUser(); - - await apiService.login(processUserModel.username, processUserModel.password); - appModel = await applicationsService.importPublishDeployApp(browser.params.resources.Files.WIDGET_CHECK_APP.file_path); - - deployedAppId = await applicationsService.getAppDefinitionId(appModel.id); - - process = await processUtil.startProcessByDefinitionName(appModel.name, app.processName); - await loginPage.login(processUserModel.username, processUserModel.password); - }); - - beforeEach(async () => { - await new ProcessServicesPage().goToAppByAppId(`${deployedAppId}`); - - await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); - await taskPage.formFields().checkFormIsDisplayed(); - }); - - afterAll(async () => { - await processUtil.cancelProcessInstance(process.id); - await apiService.loginWithProfile('admin'); - await usersActions.deleteTenant(processUserModel.tenantId); - }); - - it('[C268818] Should be able to set general settings for Date Time widget', async () => { - await expect(await widget.dateTimeWidget().getDateTimeLabel(app.FIELD.date_time_input)).toContain('Date'); - await expect(await taskPage.formFields().isCompleteFormButtonEnabled()).toEqual(false); - - await widget.dateTimeWidget().openDatepicker(app.FIELD.date_time_input); - await widget.dateTimeWidget().selectDay('10'); - await widget.dateTimeWidget().selectHour('8'); - await widget.dateTimeWidget().selectMinute('30'); - await expect(await taskPage.formFields().isCompleteFormButtonEnabled()).toEqual(true); - - await expect(await widget.dateTimeWidget().getPlaceholder(app.FIELD.date_time_between_input)).toBe('Choose anything...'); - }); - - it('[C268819] Should be able to set advanced settings for Date Time widget ', async () => { - await widget.dateTimeWidget().setDateTimeInput(app.FIELD.date_time_between_input, '20-03-17 07:30 PM'); - await widget.dateTimeWidget().closeDataTimeWidget(); - await taskPage.formFields().completeForm(); - await expect(await widget.dateTimeWidget().getErrorMessage(app.FIELD.date_time_between_input)).toContain('Can\'t be less than'); - - await browser.refresh(); - - await widget.dateTimeWidget().setDateTimeInput(app.FIELD.date_time_between_input, '20-03-19 07:30 PM'); - await widget.dateTimeWidget().closeDataTimeWidget(); - await taskPage.formFields().completeForm(); - - await expect(await widget.dateTimeWidget().getErrorMessage(app.FIELD.date_time_between_input)).toContain('Can\'t be greater than'); - }); -}); diff --git a/e2e/protractor.excludes.json b/e2e/protractor.excludes.json index 3a2383ff6c..5dcf9bf955 100644 --- a/e2e/protractor.excludes.json +++ b/e2e/protractor.excludes.json @@ -3,15 +3,9 @@ "C272819": "https://alfresco.atlassian.net/browse/ADF-5385", "C362241": "https://alfresco.atlassian.net/browse/ADF-5385", "C246534": "https://alfresco.atlassian.net/browse/ACS-4468", - "C268151": "https://alfresco.atlassian.net/browse/ACS-4467", - "C260377": "https://alfresco.atlassian.net/browse/ACS-4467", - "C260375": "https://alfresco.atlassian.net/browse/ACS-4467", - "C286290": "https://alfresco.atlassian.net/browse/ACS-4467", - "C286472": "https://alfresco.atlassian.net/browse/ACS-4467", "C260387": "https://alfresco.atlassian.net/browse/ACS-4595", "C216430": "https://alfresco.atlassian.net/browse/ACS-4595", "C280063": "https://alfresco.atlassian.net/browse/ACS-4595", "C280064": "https://alfresco.atlassian.net/browse/ACS-4595", - "C280407": "https://alfresco.atlassian.net/browse/ACS-4595", - "C277288": "https://alfresco.atlassian.net/browse/AAE-15475" + "C313200": "https://alfresco.atlassian.net/browse/APPS-2234" } diff --git a/e2e/search/components/search-sorting-picker.e2e.ts b/e2e/search/components/search-sorting-picker.e2e.ts deleted file mode 100644 index fd07bfc0b1..0000000000 --- a/e2e/search/components/search-sorting-picker.e2e.ts +++ /dev/null @@ -1,226 +0,0 @@ -/*! - * @license - * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createApiService, - LocalStorageUtil, - LoginPage, - SearchSortingPickerPage, - UploadActions, - UserModel, - UsersActions -} from '@alfresco/adf-testing'; -import { SearchBarPage } from '../pages/search-bar.page'; -import { SearchResultsPage } from '../pages/search-results.page'; -import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; -import { SearchFiltersPage } from '../pages/search-filters.page'; -import { ContentServicesPage } from '../../core/pages/content-services.page'; -import { browser } from 'protractor'; -import { SearchConfiguration } from '../search.config'; -import { NodesApi } from '@alfresco/js-api'; - -describe('Search Sorting Picker', () => { - - const loginPage = new LoginPage(); - const searchBarPage = new SearchBarPage(); - const searchFilters = new SearchFiltersPage(); - const searchResults = new SearchResultsPage(); - const navigationBarPage = new NavigationBarPage(); - const searchSortingPicker = new SearchSortingPickerPage(); - const contentServices = new ContentServicesPage(); - const acsUser = new UserModel(); - - const pngAModel = { - name: browser.params.resources.Files.ADF_DOCUMENTS.PNG.file_name, - location: browser.params.resources.Files.ADF_DOCUMENTS.PNG.file_path - }; - - const pngDModel = { - name: browser.params.resources.Files.ADF_DOCUMENTS.PNG_D.file_name, - location: browser.params.resources.Files.ADF_DOCUMENTS.PNG_D.file_path - }; - - let pngA; let pngD; - const apiService = createApiService(); - - const uploadActions = new UploadActions(apiService); - const usersActions = new UsersActions(apiService); - const nodesApi = new NodesApi(apiService.getInstance()); - - const search = '_png_file.png'; - let jsonFile; - - const checkSortingDropdownIsDisplayed = async (key = 'Modifier', label = 'Modifier') => { - await navigationBarPage.navigateToContentServices(); - jsonFile = SearchConfiguration.getConfiguration(); - jsonFile.sorting.options.push({ - key, - label, - type: 'FIELD', - field: 'cm:modifier', - ascending: true - }); - await LocalStorageUtil.setConfigField('search', JSON.stringify(jsonFile)); - - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search); - await searchResults.dataTable.waitTillContentLoaded(); - - await searchSortingPicker.checkSortingDropdownIsDisplayed(); - }; - - beforeAll(async () => { - await apiService.loginWithProfile('admin'); - await usersActions.createUser(acsUser); - - await apiService.login(acsUser.username, acsUser.password); - - pngA = await uploadActions.uploadFile(pngAModel.location, pngAModel.name, '-my-'); - pngD = await uploadActions.uploadFile(pngDModel.location, pngDModel.name, '-my-'); - await browser.sleep(browser.params.testConfig.timeouts.index_search); - - await loginPage.login(acsUser.username, acsUser.password); - }); - - afterAll(async () => { - await uploadActions.deleteFileOrFolder(pngA.entry.id); - await uploadActions.deleteFileOrFolder(pngD.entry.id); - - await navigationBarPage.clickLogoutButton(); - }); - - beforeEach(async () => { - await navigationBarPage.clickHomeButton(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search); - await searchResults.dataTable.waitTillContentLoaded(); - }); - - it(`[C277269] Should see the "sort by" option when search results are displayed in search results page`, async () => { - await searchSortingPicker.checkSortingDropdownIsDisplayed(); - }); - - it(`[C277270] Should see the icon for ASC and DESC sort when search results are displayed in the search results page`, async () => { - await searchSortingPicker.checkOrderArrowIsDisplayed(); - }); - - it('[C277271] Should be able to add a custom search sorter in the "sort by" option', async () => { - await checkSortingDropdownIsDisplayed(); - await searchSortingPicker.clickSortingDropdown(); - await searchSortingPicker.checkOptionsDropdownIsDisplayed(); - await searchSortingPicker.checkOptionIsDisplayed('Modifier'); - }); - - it('[C277272] Should be able to exclude a standard search sorter from the sorting option', async () => { - await navigationBarPage.navigateToContentServices(); - jsonFile = SearchConfiguration.getConfiguration(); - const removedOption = jsonFile.sorting.options.splice(0, 1); - await LocalStorageUtil.setConfigField('search', JSON.stringify(jsonFile)); - - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search); - await searchResults.dataTable.waitTillContentLoaded(); - - await searchSortingPicker.checkSortingDropdownIsDisplayed(); - await searchSortingPicker.clickSortingDropdown(); - await searchSortingPicker.checkOptionsDropdownIsDisplayed(); - await searchSortingPicker.checkOptionIsNotDisplayed(removedOption[0].label); - }); - - it('[C277273] Should be able to set a default order for a search sorting option', async () => { - await navigationBarPage.navigateToContentServices(); - - jsonFile = SearchConfiguration.getConfiguration(); - jsonFile.sorting.options[0].ascending = false; - jsonFile.sorting.defaults[0] = { - key: 'Size', - label: 'Size', - type: 'FIELD', - field: 'content.size', - ascending: true - }; - - await LocalStorageUtil.setConfigField('search', JSON.stringify(jsonFile)); - - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search); - await searchResults.dataTable.waitTillContentLoaded(); - - await searchSortingPicker.checkSortingDropdownIsDisplayed(); - await searchSortingPicker.clickSortingDropdown(); - await searchSortingPicker.checkOptionIsDisplayed('Name'); - await searchSortingPicker.clickSortingOption('Name'); - await expect(await searchSortingPicker.checkOrderArrowIsDownward()).toBe(true); - }); - - it('[C277280] Should be able to sort the search results by "Name" ASC', async () => { - await searchFilters.checkSearchFiltersIsDisplayed(); - await searchFilters.creatorCheckListFiltersPage().enterFilterInputValue(`${acsUser.firstName} ${acsUser.lastName}`); - await searchResults.sortByName('ASC'); - - await expect(await searchResults.checkListIsOrderedByNameAsc()).toBe(true); - }); - - it('[C277281] Should be able to sort the search results by "Name" DESC', async () => { - await searchFilters.checkSearchFiltersIsDisplayed(); - await searchFilters.creatorCheckListFiltersPage().enterFilterInputValue(`${acsUser.firstName} ${acsUser.lastName}`); - await searchResults.sortByName('DESC'); - await expect(await searchResults.checkListIsOrderedByNameDesc()).toBe(true); - }); - - it('[C277286] Should be able to sort the search results by "Created Date" ASC', async () => { - await searchResults.sortByCreated('ASC'); - const results = await searchResults.dataTable.geCellElementDetail('Created'); - await expect(contentServices.checkElementsDateSortedAsc(results)).toBe(true); - }); - - it('[C277287] Should be able to sort the search results by "Created Date" DESC', async () => { - await searchResults.sortByCreated('DESC'); - const results = await searchResults.dataTable.geCellElementDetail('Created'); - await expect(contentServices.checkElementsDateSortedDesc(results)).toBe(true); - }); - - it('[C277288] Should be able to sort the search results by "Modified Date" ASC', async () => { - await checkSortingDropdownIsDisplayed('Modified Date', 'Modified Date'); - await searchSortingPicker.sortBy('ASC', 'Modified Date'); - - const idList = await contentServices.getElementsDisplayedId(); - const numberOfElements = await contentServices.numberOfResultsDisplayed(); - - const nodeList = await getNodesDisplayed(numberOfElements, idList); - - const modifiedDateList = []; - for (const item of nodeList) { - modifiedDateList.push(new Date(item.entry.modifiedAt)); - } - - await expect(contentServices.checkElementsDateSortedAsc(modifiedDateList)).toBe(true); - }); - - const getNodesDisplayed = async function(numberOfElements: number, idList: string[]) { - const promises = []; - - for (let i = 0; i < (numberOfElements - 1); i++) { - if (idList[i] && idList[i].trim() !== '') { - promises.push(nodesApi.getNode(idList[i])); - } - } - return Promise.all(promises); - }; -}); diff --git a/e2e/search/search-page.e2e.ts b/e2e/search/search-page.e2e.ts deleted file mode 100644 index 07428d4638..0000000000 --- a/e2e/search/search-page.e2e.ts +++ /dev/null @@ -1,180 +0,0 @@ -/*! - * @license - * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { browser } from 'protractor'; - -import { createApiService, LoginPage, StringUtil, UploadActions, UserModel, UsersActions } from '@alfresco/adf-testing'; - -import { SearchBarPage } from './pages/search-bar.page'; -import { ContentServicesPage } from '../core/pages/content-services.page'; -import { SearchResultsPage } from './pages/search-results.page'; -import { FolderModel } from '../models/ACS/folder.model'; -import { FileModel } from '../models/ACS/file.model'; -import { NavigationBarPage } from '../core/pages/navigation-bar.page'; - -describe('Search component - Search Page', () => { - const search = { - active: { - firstFile: null, - secondFile: null, - base: StringUtil.generateRandomString(7), - extension: '.txt' - }, - no_permission: { - noPermFile: 'Meetings', - noPermFolder: 'Meeting Notes' - } - }; - - const loginPage = new LoginPage(); - const contentServicesPage = new ContentServicesPage(); - const searchBarPage = new SearchBarPage(); - const searchResultPage = new SearchResultsPage(); - const navigationBarPage = new NavigationBarPage(); - - const apiService = createApiService(); - const uploadActions = new UploadActions(apiService); - const usersActions = new UsersActions(apiService); - - const acsUser = new UserModel(); - const emptyFolderModel = new FolderModel({ name: 'search' + StringUtil.generateRandomString() }); - const newFolderModel = new FolderModel(); - - beforeAll(async () => { - const nrOfFiles = 15; - const adminNrOfFiles = 5; - const fileNames = StringUtil.generateFilesNames(1, nrOfFiles, search.active.base, search.active.extension); - const adminFileNames = StringUtil.generateFilesNames(nrOfFiles + 1, nrOfFiles + adminNrOfFiles, search.active.base, search.active.extension); - search.active.firstFile = fileNames[0]; - search.active.secondFile = fileNames[1]; - fileNames.splice(0, 1); - - const firstFileModel = new FileModel({ - name: search.active.firstFile, - location: browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_path - }); - - await apiService.loginWithProfile('admin'); - - await usersActions.createUser(acsUser); - await apiService.login(acsUser.username, acsUser.password); - - await uploadActions.createFolder(emptyFolderModel.name, '-my-'); - const newFolderModelUploaded = await uploadActions.createFolder(newFolderModel.name, '-my-'); - - await uploadActions.createEmptyFiles(fileNames, newFolderModelUploaded.entry.id); - - await uploadActions.uploadFile(firstFileModel.location, firstFileModel.name, '-my-'); - - await apiService.loginWithProfile('admin'); - - await uploadActions.createEmptyFiles(adminFileNames, newFolderModelUploaded.entry.id); - - await browser.sleep(browser.params.testConfig.timeouts.index_search); - - await loginPage.login(acsUser.username, acsUser.password); - }); - - afterAll(async () => { - await navigationBarPage.clickLogoutButton(); - }); - - it('[C260264] Should display message when no results are found', async () => { - const notExistentFileName = StringUtil.generateRandomString(); - await searchBarPage.checkSearchBarIsNotVisible(); - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(notExistentFileName); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkNoResultMessageIsDisplayed(); - }); - - it('[C272810] Should display only files corresponding to search', async () => { - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search.active.firstFile); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkContentIsDisplayed(search.active.firstFile); - await expect(await searchResultPage.numberOfResultsDisplayed()).toBe(1); - }); - - it('[C260267] Should display content when opening a folder from search results', async () => { - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(emptyFolderModel.name); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkNoResultMessageIsNotDisplayed(); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkContentIsDisplayed(emptyFolderModel.name); - await searchResultPage.navigateToFolder(emptyFolderModel.name); - const result = await contentServicesPage.currentFolderName(); - await expect(result).toEqual(emptyFolderModel.name); - }); - - it('[C260261] Should be able to delete a file from search results', async () => { - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search.active.firstFile); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkContentIsDisplayed(search.active.firstFile); - - await searchResultPage.deleteContent(search.active.firstFile); - - await searchResultPage.checkNoResultMessageIsDisplayed(); - await searchResultPage.checkContentIsNotDisplayed(search.active.firstFile); - - await searchBarPage.checkSearchBarIsNotVisible(); - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(search.active.firstFile); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkNoResultMessageIsDisplayed(); - }); - - it('[C272809] Should be able to delete a folder from search results', async () => { - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(emptyFolderModel.name); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkContentIsDisplayed(emptyFolderModel.name); - await searchResultPage.checkNoResultMessageIsNotDisplayed(); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkContentIsDisplayed(emptyFolderModel.name); - await searchResultPage.deleteContent(emptyFolderModel.name); - await searchResultPage.checkNoResultMessageIsDisplayed(); - - await searchBarPage.checkSearchBarIsNotVisible(); - await searchBarPage.checkSearchIconIsVisible(); - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter(emptyFolderModel.name); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkNoResultMessageIsDisplayed(); - }); - - it('[C286675] Should display results when searching for all elements', async () => { - await searchBarPage.clickOnSearchIcon(); - await searchBarPage.enterTextAndPressEnter('*'); - await searchResultPage.dataTable.waitTillContentLoaded(); - - await searchResultPage.checkNoResultMessageIsNotDisplayed(); - }); -}); diff --git a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts index 824704ef92..43aa764dd2 100644 --- a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts +++ b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts @@ -18,7 +18,7 @@ import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { DateAdapter } from '@angular/material/core'; -import { UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; +import { MomentDateAdapter, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; import { SearchWidget } from '../../models/search-widget.interface'; import { SearchWidgetSettings } from '../../models/search-widget-settings.interface'; import { SearchQueryBuilderService } from '../../services/search-query-builder.service'; @@ -26,7 +26,7 @@ import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher'; import { Moment } from 'moment'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MomentDateAdapter } from '@angular/material-moment-adapter'; +import { MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; export interface DateRangeValue { from: string; @@ -100,6 +100,9 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy .pipe(takeUntil(this.onDestroy$)) .subscribe((locale) => this.setLocale(locale)); + const customDateAdapter = this.dateAdapter as MomentDateAdapter; + customDateAdapter.overrideDisplayFormat = this.datePickerFormat; + const validators = Validators.compose([Validators.required]); if (this.settings?.maxDate) { diff --git a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts index 277cf62012..a7c9c30a7e 100644 --- a/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts +++ b/lib/core/src/lib/card-view/components/card-view-dateitem/card-view-dateitem.component.ts @@ -79,7 +79,6 @@ export class CardViewDateItemComponent extends BaseCardView { - this.dateAdapter.setLocale(locale); this.property.locale = locale; }); diff --git a/lib/core/src/lib/common/utils/date-fns-adapter.ts b/lib/core/src/lib/common/utils/date-fns-adapter.ts index f74e4b547c..7fa1321384 100644 --- a/lib/core/src/lib/common/utils/date-fns-adapter.ts +++ b/lib/core/src/lib/common/utils/date-fns-adapter.ts @@ -46,6 +46,23 @@ import { Locale } from 'date-fns'; adapter.displayFormat = ''; * } */ + +/** + * Material date formats for Date-fns + */ +export const ADF_DATE_FORMATS: MatDateFormats = { + parse: { + dateInput: 'dd-MM-yyyy' + }, + display: { + dateInput: 'dd-MM-yyyy', + monthLabel: 'LLL', + monthYearLabel: 'LLL uuuu', + dateA11yLabel: 'PP', + monthYearA11yLabel: 'LLLL uuuu' + } +}; + @Injectable() export class AdfDateFnsAdapter extends DateFnsAdapter { private _displayFormat?: string = null; diff --git a/lib/core/src/lib/common/utils/date-fns-utils.spec.ts b/lib/core/src/lib/common/utils/date-fns-utils.spec.ts index e7b228fb06..bdc7e41ed1 100644 --- a/lib/core/src/lib/common/utils/date-fns-utils.spec.ts +++ b/lib/core/src/lib/common/utils/date-fns-utils.spec.ts @@ -25,9 +25,14 @@ describe('DateFnsUtils', () => { expect(dateFnsFormat).toBe('yyyy-MM-dd'); }); - it('should convert moment datetime format', () => { + it('should convert moment datetime format with zone', () => { const dateFnsFormat = DateFnsUtils.convertMomentToDateFnsFormat('YYYY-MM-DDTHH:mm:ssZ'); - expect(dateFnsFormat).toBe(`yyyy-MM-dd'T'HH:mm:ss'Z'`); + expect(dateFnsFormat).toBe(`yyyy-MM-dd'T'HH:mm:ssXXX`); + }); + + it('should convert moment datetime format with zone hours and mins', () => { + const dateFnsFormat = DateFnsUtils.convertMomentToDateFnsFormat('YYYY-MM-DDTHH:mm:ssZZ'); + expect(dateFnsFormat).toBe(`yyyy-MM-dd'T'HH:mm:ssXX`); }); it('should convert custom moment datetime format', () => { @@ -81,20 +86,29 @@ describe('DateFnsUtils', () => { expect(result).toEqual(expectedParsedDate); }); - it('should format ISO datetime from date', () => { - const result = DateFnsUtils.formatDate( - new Date('2023-10-10T18:28:50.082Z'), - `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` + it('should parse alternative ISO datetime', () => { + const result = DateFnsUtils.parseDate( + '1982-03-13T10:00:000Z', + `yyyy-MM-dd'T'HH:mm:sssXXX` ); - expect(result).toBe('2023-10-10T18:28:50.082Z'); + + expect(result.toISOString()).toBe('1982-03-13T10:00:00.000Z'); }); - it('should format ISO datetime from string', () => { - const result = DateFnsUtils.formatDate( - '2023-10-10T18:28:50.082Z', - `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` + it('should parse the datetime with zone', () => { + const result = DateFnsUtils.parseDate( + '1982-03-13T10:00:000+01:00', + `yyyy-MM-dd'T'HH:mm:sssXXX` ); - expect(result).toBe('2023-10-10T18:28:50.082Z'); + expect(result.toISOString()).toBe('1982-03-13T09:00:00.000Z'); + }); + + it('should parse datetime with zone in moment format', () => { + const result = DateFnsUtils.parseDate( + '1982-03-13T10:00:00+0100', + `YYYY-MM-DDTHH:mm:ssZZ` + ); + expect(result.toISOString()).toBe('1982-03-13T09:00:00.000Z'); }); it('should validate datetime with moment format', () => { diff --git a/lib/core/src/lib/common/utils/date-fns-utils.ts b/lib/core/src/lib/common/utils/date-fns-utils.ts index 7767634147..ceb0db9cfc 100644 --- a/lib/core/src/lib/common/utils/date-fns-utils.ts +++ b/lib/core/src/lib/common/utils/date-fns-utils.ts @@ -87,7 +87,8 @@ export class DateFnsUtils { A: 'a', ll: 'PP', T: `'T'`, - Z: `'Z'` + ZZ: 'XX', + Z: `XXX` }; /** @@ -99,9 +100,7 @@ export class DateFnsUtils { static convertMomentToDateFnsFormat(dateDisplayFormat: string): string { if (dateDisplayFormat && dateDisplayFormat.trim() !== '') { // normalise the input to support double conversion of the same string - dateDisplayFormat = dateDisplayFormat - .replace(`'T'`, 'T') - .replace(`'Z'`, 'Z'); + dateDisplayFormat = dateDisplayFormat.replace(`'T'`, 'T'); for (const [search, replace] of Object.entries(this.momentToDateFnsMap)) { dateDisplayFormat = dateDisplayFormat.replace(new RegExp(search, 'g'), replace); @@ -194,4 +193,12 @@ export class DateFnsUtils { static isAfterDate(source: Date, target: Date): boolean { return isAfter(source, target); } + + static utcToLocal(date: Date): Date { + return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); + } + + static localToUtc(date: Date): Date { + return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); + } } diff --git a/lib/core/src/lib/common/utils/datetime-fns-adapter.ts b/lib/core/src/lib/common/utils/datetime-fns-adapter.ts new file mode 100644 index 0000000000..62777c0a27 --- /dev/null +++ b/lib/core/src/lib/common/utils/datetime-fns-adapter.ts @@ -0,0 +1,141 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { DateFnsUtils } from './date-fns-utils'; +import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimeFormats } from '@mat-datetimepicker/core'; +import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; +import { Locale, addHours, addMinutes } from 'date-fns'; + +/** + * Material date/time formats for Date-fns (mat-datetimepicker) + */ +export const ADF_DATETIME_FORMATS: MatDatetimeFormats = { + parse: { + dateInput: 'P', // L + monthInput: 'LLLL', // MMMM + timeInput: 'p', // LT + datetimeInput: 'Pp' // L LT + }, + display: { + dateInput: 'P', // L + monthInput: 'LLLL', // MMMM + datetimeInput: 'Pp', // L LT + timeInput: 'p', // LT + monthYearLabel: 'LLL uuuu', // MMM YYYY + dateA11yLabel: 'PP', // LL + monthYearA11yLabel: 'LLLL uuuu', // MMMM YYYY + popupHeaderDateLabel: 'ccc, dd MMM' // ddd, DD MMM + } +}; + +/** The default hour names to use if Intl API is not available. */ +const DEFAULT_HOUR_NAMES = range(24, (i) => String(i)); + +/** The default minute names to use if Intl API is not available. */ +const DEFAULT_MINUTE_NAMES = range(60, (i) => String(i)); + +// eslint-disable-next-line jsdoc/require-jsdoc +function range(length: number, valueFunction: (index: number) => T): T[] { + const valuesArray = Array(length); + for (let i = 0; i < length; i++) { + valuesArray[i] = valueFunction(i); + } + return valuesArray; +} + +@Injectable() +export class AdfDateTimeFnsAdapter extends DatetimeAdapter { + private _displayFormat?: string = null; + + get displayFormat(): string | null { + return this._displayFormat; + } + + set displayFormat(value: string | null) { + this._displayFormat = value ? DateFnsUtils.convertMomentToDateFnsFormat(value) : null; + } + + constructor( + @Optional() @Inject(MAT_DATE_LOCALE) matDateLocale: Locale, + @Optional() @Inject(MAT_DATETIME_FORMATS) private formats: MatDatetimeFormats, + dateAdapter: DateAdapter + ) { + super(dateAdapter); + this.setLocale(matDateLocale); + } + + getHour(date: Date): number { + return date.getHours(); + } + + getMinute(date: Date): number { + return date.getMinutes(); + } + + getFirstDateOfMonth(date: Date): Date { + const result = new Date(); + result.setFullYear(date.getFullYear(), date.getMonth(), 1); + return result; + } + + isInNextMonth(startDate: Date, endDate: Date): boolean { + const nextMonth = this.getDateInNextMonth(startDate); + return this.sameMonthAndYear(nextMonth, endDate); + } + + getHourNames(): string[] { + return DEFAULT_HOUR_NAMES; + } + + getMinuteNames(): string[] { + return DEFAULT_MINUTE_NAMES; + } + + addCalendarHours(date: Date, hours: number): Date { + return addHours(date, hours); + } + + addCalendarMinutes(date: Date, minutes: number): Date { + return addMinutes(date, minutes); + } + + createDatetime(year: number, month: number, date: number, hour: number, minute: number): Date { + const result = new Date(); + result.setFullYear(year, month, date); + result.setHours(hour, minute, 0, 0); + return result; + } + + private getDateInNextMonth(date: Date) { + return new Date(date.getFullYear(), date.getMonth() + 1, 1, date.getHours(), date.getMinutes()); + } + + override parse(value: any, parseFormat: any): Date { + return this._delegate.parse(value, parseFormat); + } + + override format(date: Date, displayFormat: any): string { + displayFormat = DateFnsUtils.convertMomentToDateFnsFormat(displayFormat); + + if (this.displayFormat && displayFormat === this.formats?.display?.datetimeInput) { + return this._delegate.format(date, this.displayFormat || displayFormat); + } + + return this._delegate.format(date, displayFormat); + } +} diff --git a/lib/core/src/lib/common/utils/moment-date-adapter.ts b/lib/core/src/lib/common/utils/moment-date-adapter.ts index b51a934af1..2ebc6234b8 100644 --- a/lib/core/src/lib/common/utils/moment-date-adapter.ts +++ b/lib/core/src/lib/common/utils/moment-date-adapter.ts @@ -18,6 +18,7 @@ import { Injectable } from '@angular/core'; import { DateAdapter } from '@angular/material/core'; import moment, { isMoment, Moment } from 'moment'; +import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service'; @Injectable() export class MomentDateAdapter extends DateAdapter { @@ -25,6 +26,14 @@ export class MomentDateAdapter extends DateAdapter { overrideDisplayFormat: string; + constructor(preferences: UserPreferencesService) { + super(); + + preferences.select(UserPreferenceValues.Locale).subscribe((locale: string) => { + this.setLocale(locale); + }); + } + getYear(date: Moment): number { return date.year(); } diff --git a/lib/core/src/lib/common/utils/public-api.ts b/lib/core/src/lib/common/utils/public-api.ts index 2c48ff567b..049b7810ef 100644 --- a/lib/core/src/lib/common/utils/public-api.ts +++ b/lib/core/src/lib/common/utils/public-api.ts @@ -22,3 +22,4 @@ export * from './moment-date-adapter'; export * from './string-utils'; export * from './date-fns-utils'; export * from './date-fns-adapter'; +export * from './datetime-fns-adapter'; diff --git a/lib/core/src/lib/core.module.ts b/lib/core/src/lib/core.module.ts index b54776216c..61d77b0acb 100644 --- a/lib/core/src/lib/core.module.ts +++ b/lib/core/src/lib/core.module.ts @@ -65,6 +65,8 @@ import { AppConfigService } from './app-config/app-config.service'; import { StorageService } from './common/services/storage.service'; import { AlfrescoApiLoaderService, createAlfrescoApiInstance } from './api-factories/alfresco-api-v2-loader.service'; import { AdfDateFnsAdapter } from './common/utils/date-fns-adapter'; +import { MomentDateAdapter } from './common/utils/moment-date-adapter'; +import { AdfDateTimeFnsAdapter } from './common/utils/datetime-fns-adapter'; @NgModule({ imports: [ @@ -150,6 +152,8 @@ export class CoreModule { TranslateService, { provide: TranslateLoader, useClass: TranslateLoaderService }, AdfDateFnsAdapter, + AdfDateTimeFnsAdapter, + MomentDateAdapter, { provide: APP_INITIALIZER, useFactory: loadAppConfig, diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts index feb578b43a..50e6aebcb1 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.spec.ts @@ -706,7 +706,7 @@ describe('FormFieldValidator', () => { }); it('should take into account that max value is in UTC and NOT fail validating value checking the time', () => { - const localValidValue = '2018-3-30 11:59 PM'; + const localValidValue = '2018-03-30T22:59:00.000Z'; const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, @@ -718,7 +718,7 @@ describe('FormFieldValidator', () => { }); it('should take into account that max value is in UTC and fail validating value checking the time', () => { - const localInvalidValue = '2018-3-31 12:01 AM'; + const localInvalidValue = '2018-03-30T23:01:00.000Z'; const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, @@ -735,8 +735,8 @@ describe('FormFieldValidator', () => { it('should succeed validating value checking the time', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '08-02-9999 09:10 AM', - maxValue: '9999-02-08 10:10 AM' + value: '9999-02-08T09:10:00.000Z', + maxValue: '9999-02-08T10:10:00.000Z' }); expect(validator.validate(field)).toBeTruthy(); @@ -745,8 +745,8 @@ describe('FormFieldValidator', () => { it('should fail validating value checking the time', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '08-02-9999 11:10 AM', - maxValue: '9999-02-08 10:10 AM' + value: '9999-02-08T11:10:00.000Z', + maxValue: '9999-02-08T10:10:00.000Z' }); field.validationSummary = new ErrorMessageModel(); @@ -757,8 +757,8 @@ describe('FormFieldValidator', () => { it('should succeed validating value checking the date', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '08-02-9999 09:10 AM', - maxValue: '9999-02-08 10:10 AM' + value: '9999-02-08T09:10:00.000Z', + maxValue: '9999-02-08T10:10:00.000Z' }); expect(validator.validate(field)).toBeTruthy(); @@ -825,12 +825,12 @@ describe('FormFieldValidator', () => { }); it('should take into account that min value is in UTC and NOT fail validating value checking the time', () => { - const localValidValue = '2018-3-02 06:01 AM'; + const localValidValue = '2018-03-02T06:01:00.000Z'; const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, value: localValidValue, - minValue: '2018-03-02T06:00:00+00:00' + minValue: '2018-03-02T06:00:00.000Z' }); expect(validator.validate(field)).toBeTruthy(); @@ -874,8 +874,8 @@ describe('FormFieldValidator', () => { it('should fail validating value by time', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '08-02-9999 09:00 AM', - minValue: '9999-02-08 09:10 AM' + value: '9999-08-02T08:10:00.000Z', + minValue: '9999-08-02T08:11:00.000Z' }); field.validationSummary = new ErrorMessageModel(); @@ -886,8 +886,8 @@ describe('FormFieldValidator', () => { it('should fail validating value by date', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '07-02-9999 09:10 AM', - minValue: '9999-02-08 09:10 AM' + value: '9999-02-07T09:10:00.000Z', + minValue: '9999-02-08T09:10:00.000Z' }); field.validationSummary = new ErrorMessageModel(); @@ -1110,9 +1110,9 @@ describe('FormFieldValidator', () => { it('should not validate dateTime format with default format', () => { const field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.DATETIME, - value: '2021-06-09 14:10' // 14:10 does not conform to A + value: '2021-06-09 14:10 AM' // 14:10 does not conform to A }); - expect(field.value).toBe('2021-06-09 14:10'); + expect(field.value).toBe('2021-06-09 14:10 AM'); expect(field.dateDisplayFormat).toBe('D-M-YYYY hh:mm A'); expect(validator.validate(field)).toBeFalse(); }); diff --git a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts index dd663280a4..574eca70b8 100644 --- a/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts +++ b/lib/core/src/lib/form/components/widgets/core/form-field-validator.ts @@ -21,6 +21,7 @@ import { FormFieldTypes } from './form-field-types'; import { isNumberValue } from './form-field-utils'; import { FormFieldModel } from './form-field.model'; import { DateFnsUtils } from '../../../../common/utils/date-fns-utils'; +import { isValid as isDateValid, isBefore, isAfter } from 'date-fns'; export interface FormFieldValidator { @@ -171,18 +172,18 @@ export class DateTimeFieldValidator implements FormFieldValidator { FormFieldTypes.DATETIME ]; - // Validates that the input string is a valid date formatted as (default D-M-YYYY) - static isValidDate(inputDate: string, dateFormat: string = 'YYYY-MM-DD HH:mm'): boolean { - return DateFnsUtils.isValidDate(inputDate, dateFormat); - } - isSupported(field: FormFieldModel): boolean { return field && this.supportedTypes.indexOf(field.type) > -1; } + static isValidDateTime(input: string): boolean { + const date = new Date(input); + return isDateValid(date); + } + validate(field: FormFieldModel): boolean { if (this.isSupported(field) && field.value && field.isVisible) { - if (DateFieldValidator.isValidDate(field.value, field.dateDisplayFormat)) { + if (DateTimeFieldValidator.isValidDateTime(field.value)) { return true; } field.validationSummary.message = field.dateDisplayFormat; @@ -298,24 +299,22 @@ export class MinDateTimeFieldValidator implements FormFieldValidator { validate(field: FormFieldModel): boolean { let isValid = true; if (this.isSupported(field) && field.value && field.isVisible) { - const dateFormat = field.dateDisplayFormat; - - if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { + if (!DateTimeFieldValidator.isValidDateTime(field.value)) { field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE'; isValid = false; } else { - isValid = this.checkDateTime(field, dateFormat); + isValid = this.checkDateTime(field); } } return isValid; } - private checkDateTime(field: FormFieldModel, dateFormat: string): boolean { + private checkDateTime(field: FormFieldModel): boolean { let isValid = true; - const fieldValueDate = DateFnsUtils.parseDate(field.value, dateFormat); + const fieldValueDate = new Date(field.value); const min = new Date(field.minValue); - if (DateFnsUtils.isBeforeDate(fieldValueDate, min)) { + if (isBefore(fieldValueDate, min)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`; field.validationSummary.attributes.set( 'minValue', @@ -349,24 +348,22 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator { validate(field: FormFieldModel): boolean { let isValid = true; if (this.isSupported(field) && field.value && field.isVisible) { - const dateFormat = field.dateDisplayFormat; - - if (!DateFieldValidator.isValidDate(field.value, dateFormat)) { + if (!DateTimeFieldValidator.isValidDateTime(field.value)) { field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE'; isValid = false; } else { - isValid = this.checkDateTime(field, dateFormat); + isValid = this.checkDateTime(field); } } return isValid; } - private checkDateTime(field: FormFieldModel, dateFormat: string): boolean { + private checkDateTime(field: FormFieldModel): boolean { let isValid = true; - const fieldValueDate = DateFnsUtils.parseDate(field.value, dateFormat); + const fieldValueDate = new Date(field.value); const max = new Date(field.maxValue); - if (DateFnsUtils.isAfterDate(fieldValueDate, max)) { + if (isAfter(fieldValueDate, max)) { field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`; field.validationSummary.attributes.set( 'maxValue', diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html index f2081c71ad..85541b22ea 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.html @@ -6,29 +6,30 @@ + [matDatetimepicker]="datetimePicker" + [id]="field.id" + [(ngModel)]="value" + [required]="isRequired()" + [disabled]="field.readOnly" + (change)="onValueChanged($event)" + (dateChange)="onDateChanged($event)" + [placeholder]="field.placeholder" + [matTooltip]="field.tooltip" + (blur)="markAsTouched()" + matTooltipPosition="above" + matTooltipShowDelay="1000" + [min]="minDate" + [max]="maxDate"> - - + + diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts index 3c91a1cb6f..bf26c78ff1 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.spec.ts @@ -16,7 +16,6 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import moment from 'moment'; import { FormFieldModel } from '../core/form-field.model'; import { FormModel } from '../core/form.model'; import { DateTimeWidgetComponent } from './date-time.widget'; @@ -25,12 +24,14 @@ import { TranslateModule } from '@ngx-translate/core'; import { MatTooltipModule } from '@angular/material/tooltip'; import { FormFieldTypes } from '../core/form-field-types'; import { By } from '@angular/platform-browser'; +import { DateFieldValidator, DateTimeFieldValidator } from '../core'; describe('DateTimeWidgetComponent', () => { let widget: DateTimeWidgetComponent; let fixture: ComponentFixture; let element: HTMLElement; + let form: FormModel; beforeEach(() => { TestBed.configureTestingModule({ @@ -44,6 +45,9 @@ describe('DateTimeWidgetComponent', () => { element = fixture.nativeElement; widget = fixture.componentInstance; + + form = new FormModel(); + form.fieldValidators = [new DateFieldValidator(), new DateTimeFieldValidator() ]; }); afterEach(() => { @@ -51,9 +55,9 @@ describe('DateTimeWidgetComponent', () => { TestBed.resetTestingModule(); }); - it('should setup min value for date picker', () => { - const minValue = '1982-03-13T10:00:000Z'; - widget.field = new FormFieldModel(null, { + it('should setup min value for date picker', async () => { + const minValue = '1982-03-13T10:00:00Z'; + widget.field = new FormFieldModel(form, { id: 'date-id', name: 'date-name', type: 'datetime', @@ -61,13 +65,13 @@ describe('DateTimeWidgetComponent', () => { }); fixture.detectChanges(); + await fixture.whenStable(); - const expected = moment(minValue, 'YYYY-MM-DDTHH:mm:ssZ'); - expect(widget.minDate.isSame(expected)).toBeTruthy(); + expect(widget.minDate.toISOString()).toBe(`1982-03-13T10:00:00.000Z`); }); it('should date field be present', () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { id: 'date-id', name: 'date-name', type: 'datetime' @@ -78,35 +82,161 @@ describe('DateTimeWidgetComponent', () => { expect(element.querySelector('#data-time-widget')).not.toBeNull(); }); - it('should setup max value for date picker', () => { - const maxValue = '1982-03-13T10:00:000Z'; + it('should setup max value for date picker', async () => { + const maxValue = '1982-03-13T10:00:00Z'; widget.field = new FormFieldModel(null, { maxValue }); fixture.detectChanges(); + await fixture.whenStable(); - const expected = moment(maxValue, 'YYYY-MM-DDTHH:mm:ssZ'); - expect(widget.maxDate.isSame(expected)).toBeTruthy(); + expect(widget.maxDate.toISOString()).toBe('1982-03-13T10:00:00.000Z'); }); it('should eval visibility on date changed', () => { spyOn(widget, 'onFieldChanged').and.callThrough(); - const field = new FormFieldModel(new FormModel(), { + const field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '09-12-9999 10:00 AM', - type: 'datetime', - readOnly: 'false' + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' }); widget.field = field; - const mockDate = moment('1982-03-13T10:00:000Z', 'YYYY-MM-DDTHH:mm:ssZ'); - widget.onDateChanged(mockDate); + widget.onDateChanged({ value: new Date('1982-03-13T10:00:00.000Z') } as any); expect(widget.onFieldChanged).toHaveBeenCalledWith(field); }); + it('should validate the initial datetime value', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.whenStable(); + await fixture.whenStable(); + + expect(field.isValid).toBeTrue(); + }); + + it('should validate the updated datetime value', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.whenStable(); + await fixture.whenStable(); + + widget.onDateChanged({ value: new Date('9999-09-12T09:10:00.000Z') } as any); + + expect(field.value).toBe('9999-09-12T09:10:00.000Z'); + expect(field.isValid).toBeTrue(); + }); + + it('should forwad the incorrect datetime input for further validation', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.detectChanges(); + await fixture.whenStable(); + + widget.onDateChanged({ + value: null, + targetElement: { + value: '123abc' + } + } as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(field.value).toBe('123abc'); + expect(field.isValid).toBeFalse(); + expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A'); + }); + + it('should process direct keyboard input', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.whenStable(); + await fixture.whenStable(); + + widget.onValueChanged({ target: { value: '9999-09-12T09:10:00.000Z' } } as any); + + expect(field.value).toBe('9999-09-12T09:10:00.000Z'); + expect(field.isValid).toBeTrue(); + }); + + it('should fail validating incorrect keyboard input', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.detectChanges(); + await fixture.whenStable(); + + widget.onValueChanged({ + target: { + value: '123abc' + } + } as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(field.value).toBe('123abc'); + expect(field.isValid).toBeFalse(); + expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A'); + }); + + it('should allow empty dates when not required', async () => { + const field = new FormFieldModel(form, { + id: 'date-field-id', + name: 'date-name', + value: '9999-09-12T09:00:00.000Z', + type: 'datetime' + }); + + widget.field = field; + + fixture.whenStable(); + await fixture.whenStable(); + + widget.onDateChanged({ value: null, targetElement: { value: '' } } as any); + + expect(field.value).toBe(''); + expect(field.isValid).toBeTrue(); + }); + describe('when tooltip is set', () => { beforeEach(() => { @@ -166,97 +296,95 @@ describe('DateTimeWidgetComponent', () => { it('should be able to display label with asterisk', () => { fixture.detectChanges(); - const asterisk: HTMLElement = element.querySelector('.adf-asterisk'); + const asterisk = element.querySelector('.adf-asterisk'); - expect(asterisk).toBeTruthy(); - expect(asterisk.textContent).toEqual('*'); + expect(asterisk).not.toBeNull(); + expect(asterisk?.textContent).toEqual('*'); }); }); describe('template check', () => { - it('should show visible date widget', () => { - widget.field = new FormFieldModel(new FormModel(), { + it('should show visible date widget', async () => { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '30-11-9999 10:30 AM', - type: 'datetime', - readOnly: 'false' + value: '9999-11-30T10:30:00.000Z', + type: 'datetime' }); fixture.detectChanges(); + await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); - - const dateElement: any = element.querySelector('#date-field-id'); - expect(dateElement.value).toBe('30-11-9999 10:30 AM'); + const dateElement = element.querySelector('#date-field-id'); + expect(dateElement).not.toBeNull(); + expect(dateElement?.value).toBe('30-11-9999 10:30 AM'); }); - it('should show the correct format type', () => { - widget.field = new FormFieldModel(new FormModel(), { + it('should show the correct format type', async () => { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '12-30-9999 10:30 AM', + value: '9999-12-30T10:30:00.000Z', dateDisplayFormat: 'MM-DD-YYYY HH:mm A', - type: 'datetime', - readOnly: 'false' + type: 'datetime' }); fixture.detectChanges(); + await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); - - const dateElement: any = element.querySelector('#date-field-id'); - expect(dateElement.value).toContain('12-30-9999 10:30 AM'); + const dateElement = element.querySelector('#date-field-id'); + expect(dateElement).not.toBeNull(); + expect(dateElement?.value).toContain('12-30-9999 10:30 AM'); }); it('should disable date button when is readonly', () => { - widget.field = new FormFieldModel(new FormModel(), { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '12-30-9999 10:30 AM', + value: '9999-12-30T10:30:00.000Z', dateDisplayFormat: 'MM-DD-YYYY HH:mm A', - type: 'datetime', - readOnly: 'false' + type: 'datetime' }); fixture.detectChanges(); let dateButton = element.querySelector('button'); - expect(dateButton.disabled).toBeFalsy(); + expect(dateButton).not.toBeNull(); + expect(dateButton?.disabled).toBeFalsy(); widget.field.readOnly = true; fixture.detectChanges(); dateButton = element.querySelector('button'); - expect(dateButton.disabled).toBeTruthy(); + expect(dateButton).not.toBeNull(); + expect(dateButton?.disabled).toBeTruthy(); }); }); - it('should display always the json value', () => { - const field = new FormFieldModel(new FormModel(), { + it('should display always the json value', async () => { + const field = new FormFieldModel(form, { id: 'date-field-id', name: 'datetime-field-name', - value: '12-30-9999 10:30 AM', + value: '9999-12-30T10:30:00.000Z', type: 'datetime', - readOnly: 'false' + dateDisplayFormat: 'MM-DD-YYYY HH:mm A' }); - field.isVisible = true; - field.dateDisplayFormat = 'MM-DD-YYYY HH:mm A'; widget.field = field; + fixture.detectChanges(); + await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); + const dateElement = element.querySelector('#date-field-id'); + expect(dateElement).not.toBeNull(); + expect(dateElement?.value).toContain('12-30-9999 10:30 AM'); - const dateElement: any = element.querySelector('#date-field-id'); - expect(dateElement.value).toContain('12-30-9999 10:30 AM'); + widget.field.value = '2020-03-02T00:00:00.000Z'; - widget.field.value = '03-02-2020 12:00 AM'; + fixture.componentInstance.ngOnInit(); fixture.detectChanges(); + await fixture.whenStable(); - expect(dateElement.value).toContain('03-02-2020 12:00 AM'); + expect(dateElement?.value).toContain('03-02-2020 00:00 AM'); }); describe('when form model has left labels', () => { diff --git a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts index cd8a60f8b7..0762b7afaa 100644 --- a/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts +++ b/lib/core/src/lib/form/components/widgets/date-time/date-time.widget.ts @@ -17,76 +17,88 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; -import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; -import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; -import moment, { Moment } from 'moment'; -import { UserPreferencesService, UserPreferenceValues } from '../../../../common/services/user-preferences.service'; -import { MomentDateAdapter } from '../../../../common/utils/moment-date-adapter'; -import { MOMENT_DATE_FORMATS } from '../../../../common/utils/moment-date-formats.model'; +import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerInputEvent } from '@mat-datetimepicker/core'; import { FormService } from '../../../services/form.service'; import { WidgetComponent } from '../widget.component'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { ADF_DATE_FORMATS, AdfDateFnsAdapter } from '../../../../common/utils/date-fns-adapter'; +import { ADF_DATETIME_FORMATS, AdfDateTimeFnsAdapter } from '../../../../common/utils/datetime-fns-adapter'; +import { DateFnsUtils } from '../../../../common'; +import { isValid } from 'date-fns'; @Component({ providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }, - { provide: DatetimeAdapter, useClass: MomentDatetimeAdapter }, - { provide: MAT_DATETIME_FORMATS, useValue: MAT_MOMENT_DATETIME_FORMATS } + { provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS }, + { provide: MAT_DATETIME_FORMATS, useValue: ADF_DATETIME_FORMATS }, + { provide: DateAdapter, useClass: AdfDateFnsAdapter }, + { provide: DatetimeAdapter, useClass: AdfDateTimeFnsAdapter } ], selector: 'date-time-widget', templateUrl: './date-time.widget.html', styleUrls: ['./date-time.widget.scss'], encapsulation: ViewEncapsulation.None }) -export class DateTimeWidgetComponent extends WidgetComponent implements OnInit, OnDestroy { +export class DateTimeWidgetComponent extends WidgetComponent implements OnInit { + minDate: Date; + maxDate: Date; - minDate: Moment; - maxDate: Moment; - - private onDestroy$ = new Subject(); + @Input() + value: any = null; constructor(public formService: FormService, - private dateAdapter: DateAdapter, - private userPreferencesService: UserPreferencesService) { + private dateAdapter: DateAdapter, + private dateTimeAdapter: DatetimeAdapter) { super(formService); } ngOnInit() { - this.userPreferencesService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.dateAdapter.setLocale(locale)); + if (this.field.dateDisplayFormat) { + const dateAdapter = this.dateAdapter as AdfDateFnsAdapter; + dateAdapter.displayFormat = this.field.dateDisplayFormat; - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; + const dateTimeAdapter = this.dateTimeAdapter as AdfDateTimeFnsAdapter; + dateTimeAdapter.displayFormat = this.field.dateDisplayFormat; + } if (this.field) { if (this.field.minValue) { - this.minDate = moment.utc(this.field.minValue, 'YYYY-MM-DDTHH:mm:ssZ'); + this.minDate = DateFnsUtils.localToUtc(new Date(this.field.minValue)); } if (this.field.maxValue) { - this.maxDate = moment.utc(this.field.maxValue, 'YYYY-MM-DDTHH:mm:ssZ'); + this.maxDate = DateFnsUtils.localToUtc(new Date(this.field.maxValue)); + } + + if (this.field.value) { + this.value = DateFnsUtils.localToUtc(new Date(this.field.value)); } } } - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); + onValueChanged(event: Event) { + const input = event.target as HTMLInputElement; + const newValue = this.dateTimeAdapter.parse(input.value, this.field.dateDisplayFormat); + + if (isValid(newValue)) { + this.field.value = DateFnsUtils.utcToLocal(newValue).toISOString(); + } else { + this.field.value = input.value; + } + + this.onFieldChanged(this.field); } - onDateChanged(newDateValue) { - const date = moment(newDateValue, this.field.dateDisplayFormat, true); - if (date.isValid()) { - this.field.value = moment(date).utc().local().format(this.field.dateDisplayFormat); + onDateChanged(event: MatDatetimepickerInputEvent) { + const newValue = event.value; + const input = event.targetElement as HTMLInputElement; + + if (newValue && isValid(newValue)) { + this.field.value = DateFnsUtils.utcToLocal(newValue).toISOString(); } else { - this.field.value = newDateValue; + this.field.value = input.value; } + this.onFieldChanged(this.field); } } diff --git a/lib/core/src/lib/form/components/widgets/date/date.widget.ts b/lib/core/src/lib/form/components/widgets/date/date.widget.ts index 3fa5818e65..1ba6105f51 100644 --- a/lib/core/src/lib/form/components/widgets/date/date.widget.ts +++ b/lib/core/src/lib/form/components/widgets/date/date.widget.ts @@ -23,13 +23,12 @@ import { FormService } from '../../../services/form.service'; import { WidgetComponent } from '../widget.component'; import { Subject } from 'rxjs'; import { MatDatepickerInputEvent } from '@angular/material/datepicker'; -import { ADF_FORM_DATE_FORMATS } from '../../../date-formats'; -import { AdfDateFnsAdapter } from '../../../../common/utils/date-fns-adapter'; +import { ADF_DATE_FORMATS, AdfDateFnsAdapter } from '../../../../common/utils/date-fns-adapter'; @Component({ selector: 'date-widget', providers: [ - { provide: MAT_DATE_FORMATS, useValue: ADF_FORM_DATE_FORMATS }, + { provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS }, { provide: DateAdapter, useClass: AdfDateFnsAdapter } ], templateUrl: './date.widget.html', diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html index f65fb13fa0..a36a586a4b 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.html @@ -7,29 +7,26 @@ - - + + + + - - - + diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts index 832f7757c3..da8406b858 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.spec.ts @@ -17,18 +17,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DateCloudWidgetComponent } from './date-cloud.widget'; -import { FormFieldModel, FormModel, FormFieldTypes } from '@alfresco/adf-core'; -import moment from 'moment'; +import { FormFieldModel, FormModel, FormFieldTypes, DateFieldValidator, MinDateFieldValidator, MaxDateFieldValidator } from '@alfresco/adf-core'; import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; import { TranslateModule } from '@ngx-translate/core'; -import { DATE_FORMAT_CLOUD } from '../../../../models/date-format-cloud.model'; import { By } from '@angular/platform-browser'; +import { DateAdapter } from '@angular/material/core'; +import { isEqual, subDays, addDays } from 'date-fns'; describe('DateWidgetComponent', () => { let widget: DateCloudWidgetComponent; let fixture: ComponentFixture; let element: HTMLElement; + let adapter: DateAdapter; + let form: FormModel; beforeEach(() => { TestBed.configureTestingModule({ @@ -37,7 +39,13 @@ describe('DateWidgetComponent', () => { ProcessServiceCloudTestingModule ] }); + + form = new FormModel(); + form.fieldValidators = [new DateFieldValidator(), new MinDateFieldValidator(), new MaxDateFieldValidator()]; + fixture = TestBed.createComponent(DateCloudWidgetComponent); + adapter = fixture.debugElement.injector.get(DateAdapter); + widget = fixture.componentInstance; element = fixture.nativeElement; }); @@ -52,13 +60,14 @@ describe('DateWidgetComponent', () => { widget.ngOnInit(); - const expected = moment(minValue, DATE_FORMAT_CLOUD); - expect(widget.minDate.isSame(expected)).toBeTruthy(); + const expected = adapter.parse(minValue, widget.DATE_FORMAT); + expect(isEqual(widget.minDate, expected)).toBeTrue(); }); it('should date field be present', () => { const minValue = '1982-03-13'; - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, minValue }); @@ -70,29 +79,29 @@ describe('DateWidgetComponent', () => { it('should setup max value for date picker', () => { const maxValue = '1982-03-13'; - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, maxValue }); widget.ngOnInit(); - const expected = moment(maxValue, DATE_FORMAT_CLOUD); - expect(widget.maxDate.isSame(expected)).toBeTruthy(); + const expected = adapter.parse(maxValue, widget.DATE_FORMAT); + expect(isEqual(widget.maxDate, expected)).toBeTrue(); }); it('should eval visibility on date changed', () => { spyOn(widget, 'onFieldChanged').and.callThrough(); - const field = new FormFieldModel(new FormModel(), { + const field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, id: 'date-field-id', name: 'date-name', value: '9999-9-9', - type: 'date', readOnly: 'false' }); widget.field = field; - const todayDate = moment().format(DATE_FORMAT_CLOUD); - widget.onDateChanged({ value: todayDate }); + widget.onDateChanged({ value: adapter.today() } as any); expect(widget.onFieldChanged).toHaveBeenCalledWith(field); }); @@ -105,48 +114,46 @@ describe('DateWidgetComponent', () => { }); it('should show visible date widget', async () => { - widget.field = new FormFieldModel(new FormModel(), { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', + // always stored as dd-MM-yyyy value: '9999-9-9', - type: 'date', - readOnly: 'false' + type: FormFieldTypes.DATE }); - widget.field.isVisible = true; - widget.ngOnInit(); + fixture.detectChanges(); await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); + const dateElement = element.querySelector('#date-field-id'); - expect(dateElement.value).toContain('9-9-9999'); + expect(dateElement).not.toBeNull(); + + expect(dateElement?.value).toContain('9-9-9999'); }); it('should show the correct format type', async () => { - widget.field = new FormFieldModel(new FormModel(), { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '9999-30-12', - type: 'date', - readOnly: 'false' + // always stored as dd-MM-yyyy + value: '30-12-9999', + type: FormFieldTypes.DATE, + dateDisplayFormat: 'YYYY-DD-MM' }); - widget.field.isVisible = true; - widget.field.dateDisplayFormat = 'YYYY-DD-MM'; - widget.ngOnInit(); + fixture.detectChanges(); await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); + const dateElement = element.querySelector('#date-field-id'); expect(dateElement.value).toContain('9999-30-12'); }); it('should disable date button when is readonly', async () => { - widget.field = new FormFieldModel(new FormModel(), { + widget.field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', value: '9999-9-9', - type: 'date', + type: FormFieldTypes.DATE, readOnly: 'false' }); widget.field.isVisible = true; @@ -170,7 +177,7 @@ describe('DateWidgetComponent', () => { id: 'date-field-id', name: 'date-name', value: 'aa', - type: 'date', + type: FormFieldTypes.DATE, readOnly: 'false' }); widget.field.isVisible = true; @@ -184,31 +191,31 @@ describe('DateWidgetComponent', () => { }); it('should display always the json value', async () => { - const field = new FormFieldModel(new FormModel(), { + const field = new FormFieldModel(form, { id: 'date-field-id', name: 'date-name', - value: '12-30-9999', - type: 'date', - readOnly: 'false' + // always stored as dd-MM-yyyy + value: '30-12-9999', + type: FormFieldTypes.DATE, + readOnly: 'false', + dateDisplayFormat: 'MM-DD-YYYY' }); - field.isVisible = true; - field.dateDisplayFormat = 'MM-DD-YYYY'; widget.field = field; - widget.ngOnInit(); + fixture.detectChanges(); await fixture.whenStable(); - expect(element.querySelector('#date-field-id')).toBeDefined(); - expect(element.querySelector('#date-field-id')).not.toBeNull(); - const dateElement: any = element.querySelector('#date-field-id'); + const dateElement = element.querySelector('#date-field-id'); + expect(dateElement).toBeDefined(); expect(dateElement.value).toContain('12-30-9999'); widget.field.value = '03-02-2020'; + fixture.componentInstance.ngOnInit(); fixture.detectChanges(); await fixture.whenStable(); - expect(dateElement.value).toContain('03-02-2020'); + expect(dateElement.value).toContain('02-03-2020'); }); describe('when form model has left labels', () => { @@ -285,7 +292,8 @@ describe('DateWidgetComponent', () => { describe('Set dynamic dates', () => { it('should min date equal to the today date minus minimum date range value', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, minDateRangeValue: 4 }); @@ -293,13 +301,13 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const todayDate = moment().format(DATE_FORMAT_CLOUD); - const expected = moment(todayDate).subtract(widget.field.minDateRangeValue, 'days'); - expect(widget.minDate).toEqual(expected); + const expected = subDays(adapter.today(), widget.field.minDateRangeValue); + expect(widget.minDate.toDateString()).toBe(expected.toDateString()); }); it('should min date and max date be undefined if dynamic min and max date are not set', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true }); @@ -311,7 +319,8 @@ describe('DateWidgetComponent', () => { }); it('should max date be undefined if only minimum date range value is set', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, minDateRangeValue: 4 }); @@ -323,7 +332,8 @@ describe('DateWidgetComponent', () => { }); it('should min date be undefined if only maximum date range value is set', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: 4 }); @@ -335,7 +345,8 @@ describe('DateWidgetComponent', () => { }); it('should max date equal to the today date plus maximum date range value', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: 5 }); @@ -343,13 +354,13 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const todayDate = moment().format(DATE_FORMAT_CLOUD); - const expected = moment(todayDate).add(widget.field.maxDateRangeValue, 'days'); - expect(widget.maxDate).toEqual(expected); + const expected = addDays(adapter.today(), widget.field.maxDateRangeValue); + expect(widget.maxDate.toDateString()).toBe(expected.toDateString()); }); it('should maxDate and minDate be undefined if minDateRangeValue and maxDateRangeValue are null', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: null, minDateRangeValue: null @@ -363,7 +374,8 @@ describe('DateWidgetComponent', () => { }); it('should minDate be undefined if minDateRangeValue is null and maxDateRangeValue is greater than 0', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: 15, minDateRangeValue: null @@ -377,7 +389,8 @@ describe('DateWidgetComponent', () => { }); it('should maxDate be undefined if maxDateRangeValue is null and minDateRangeValue is greater than 0', async () => { - widget.field = new FormFieldModel(null, { + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: null, minDateRangeValue: 10 @@ -392,8 +405,10 @@ describe('DateWidgetComponent', () => { describe('check date validation by dynamic date ranges', () => { it('should minValue be equal to today date minus minDateRangeValue', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); - widget.field = new FormFieldModel(null, { + spyOn(adapter, 'today').and.returnValue(new Date('2022-07-22')); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: null, minDateRangeValue: 1, @@ -404,16 +419,16 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMinValueString = '2022-07-21'; - - expect(widget.field.minValue).toEqual(expectedMinValueString); + expect(widget.field.minValue).toEqual('21-07-2022'); expect(widget.maxDate).toBeUndefined(); expect(widget.field.maxValue).toBeNull(); }); it('should maxValue be equal to today date plus maxDateRangeValue', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); - widget.field = new FormFieldModel(null, { + spyOn(adapter, 'today').and.returnValue(new Date('2022-07-22')); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: 8, minDateRangeValue: null, @@ -424,16 +439,16 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMaxValueString = '2022-07-30'; - - expect(widget.field.maxValue).toEqual(expectedMaxValueString); + expect(widget.field.maxValue).toEqual('30-07-2022'); expect(widget.minDate).toBeUndefined(); expect(widget.field.minValue).toBeNull(); }); it('should maxValue and minValue be null if maxDateRangeValue and minDateRangeValue are null', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); - widget.field = new FormFieldModel(null, { + spyOn(adapter, 'today').and.returnValue(new Date('2022-07-22')); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: null, minDateRangeValue: null, @@ -451,8 +466,10 @@ describe('DateWidgetComponent', () => { }); it('should maxValue and minValue not be null if maxDateRangeVale and minDateRangeValue are not null', async () => { - spyOn(widget, 'getTodaysFormattedDate').and.returnValue('2022-07-22'); - widget.field = new FormFieldModel(null, { + spyOn(adapter, 'today').and.returnValue(new Date('2022-07-22')); + + widget.field = new FormFieldModel(form, { + type: FormFieldTypes.DATE, dynamicDateRangeSelection: true, maxDateRangeValue: 8, minDateRangeValue: 10, @@ -463,11 +480,8 @@ describe('DateWidgetComponent', () => { fixture.detectChanges(); await fixture.whenStable(); - const expectedMaxValueString = '2022-07-30'; - const expectedMinValueString = '2022-07-12'; - - expect(widget.field.maxValue).toEqual(expectedMaxValueString); - expect(widget.field.minValue).toEqual(expectedMinValueString); + expect(widget.field.minValue).toEqual('12-07-2022'); + expect(widget.field.maxValue).toEqual('30-07-2022'); }); }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts index 8d62144249..9043723b8e 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/date/date-cloud.widget.ts @@ -17,22 +17,20 @@ /* eslint-disable @angular-eslint/component-selector */ -import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation, OnDestroy, Input } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; -import moment, { Moment } from 'moment'; import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { - MOMENT_DATE_FORMATS, MomentDateAdapter, WidgetComponent, - UserPreferencesService, UserPreferenceValues, FormService -} from '@alfresco/adf-core'; -import { DATE_FORMAT_CLOUD } from '../../../../models/date-format-cloud.model'; +import { WidgetComponent, FormService, AdfDateFnsAdapter, DateFnsUtils } from '@alfresco/adf-core'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; +import { CLOUD_FORM_DATE_FORMATS } from '../../../date-formats'; +import { addDays, subDays } from 'date-fns'; @Component({ selector: 'date-widget', providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }], + { provide: MAT_DATE_FORMATS, useValue: CLOUD_FORM_DATE_FORMATS }, + { provide: DateAdapter, useClass: AdfDateFnsAdapter } + ], templateUrl: './date-cloud.widget.html', styleUrls: ['./date-cloud.widget.scss'], host: { @@ -50,52 +48,58 @@ import { DATE_FORMAT_CLOUD } from '../../../../models/date-format-cloud.model'; }) export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, OnDestroy { typeId = 'DateCloudWidgetComponent'; + readonly DATE_FORMAT = 'dd-MM-yyyy'; - minDate: Moment; - maxDate: Moment; + minDate: Date; + maxDate: Date; + startAt: Date; + + /** + * Current date value. + * The value is always stored in the format `dd-MM-yyyy`, + * but displayed in the UI component using `dateDisplayFormat` + */ + @Input() + value: any = null; private onDestroy$ = new Subject(); constructor(public formService: FormService, - private dateAdapter: DateAdapter, - private userPreferencesService: UserPreferencesService) { + private dateAdapter: DateAdapter) { super(formService); } ngOnInit() { - this.userPreferencesService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.dateAdapter.setLocale(locale)); - - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.field.dateDisplayFormat; + if (this.field.dateDisplayFormat) { + const adapter = this.dateAdapter as AdfDateFnsAdapter; + adapter.displayFormat = this.field.dateDisplayFormat; + } if (this.field) { if (this.field.dynamicDateRangeSelection) { - const today = this.getTodaysFormattedDate(); if (Number.isInteger(this.field.minDateRangeValue)) { - this.minDate = moment(today).subtract(this.field.minDateRangeValue, 'days'); - this.field.minValue = this.minDate.format(DATE_FORMAT_CLOUD); + this.minDate = subDays(this.dateAdapter.today(), this.field.minDateRangeValue); + this.field.minValue = DateFnsUtils.formatDate(this.minDate, this.DATE_FORMAT); } if (Number.isInteger(this.field.maxDateRangeValue)) { - this.maxDate = moment(today).add(this.field.maxDateRangeValue, 'days'); - this.field.maxValue = this.maxDate.format(DATE_FORMAT_CLOUD); + this.maxDate = addDays(this.dateAdapter.today(), this.field.maxDateRangeValue); + this.field.maxValue = DateFnsUtils.formatDate(this.maxDate, this.DATE_FORMAT); } } else { if (this.field.minValue) { - this.minDate = moment(this.field.minValue, DATE_FORMAT_CLOUD); + this.minDate = this.dateAdapter.parse(this.field.minValue, this.DATE_FORMAT); } if (this.field.maxValue) { - this.maxDate = moment(this.field.maxValue, DATE_FORMAT_CLOUD); + this.maxDate = this.dateAdapter.parse(this.field.maxValue, this.DATE_FORMAT); } } - } - } - getTodaysFormattedDate() { - return moment().format(DATE_FORMAT_CLOUD); + if (this.field.value) { + this.startAt = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT); + this.value = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT); + } + } } ngOnDestroy() { @@ -103,13 +107,16 @@ export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, this.onDestroy$.complete(); } - onDateChanged(newDateValue) { - const date = moment(newDateValue, this.field.dateDisplayFormat, true); - if (date.isValid()) { - this.field.value = date.format(this.field.dateDisplayFormat); + onDateChanged(event: MatDatepickerInputEvent) { + const value = event.value; + const input = event.targetElement as HTMLInputElement; + + if (value) { + this.field.value = this.dateAdapter.format(value, this.DATE_FORMAT); } else { - this.field.value = newDateValue; + this.field.value = input.value; } + this.onFieldChanged(this.field); } } diff --git a/lib/core/src/lib/form/date-formats.ts b/lib/process-services-cloud/src/lib/form/date-formats.ts similarity index 69% rename from lib/core/src/lib/form/date-formats.ts rename to lib/process-services-cloud/src/lib/form/date-formats.ts index eba4aed87d..c65b84caf0 100644 --- a/lib/core/src/lib/form/date-formats.ts +++ b/lib/process-services-cloud/src/lib/form/date-formats.ts @@ -17,12 +17,19 @@ import { MatDateFormats } from '@angular/material/core'; -export const ADF_FORM_DATE_FORMATS: MatDateFormats = { +/** + * Provides date/time display formatting for the cloud components. + * + * Notes for developers: display formats are different from the storage formats. + * Components have a fixed format for saving dates and datetime values, + * while dynamic format for UI display. + */ +export const CLOUD_FORM_DATE_FORMATS: MatDateFormats = { parse: { - dateInput: 'dd-MM-yyyy' + dateInput: 'yyyy-MM-dd' }, display: { - dateInput: 'dd-MM-yyyy', + dateInput: 'yyyy-MM-dd', monthLabel: 'LLL', monthYearLabel: 'LLL uuuu', dateA11yLabel: 'PP', diff --git a/lib/process-services-cloud/src/lib/form/public-api.ts b/lib/process-services-cloud/src/lib/form/public-api.ts index deea686958..aede55d974 100644 --- a/lib/process-services-cloud/src/lib/form/public-api.ts +++ b/lib/process-services-cloud/src/lib/form/public-api.ts @@ -40,3 +40,4 @@ export * from './services/content-cloud-node-selector.service'; export * from './services/process-cloud-content.service'; export * from './form-cloud.module'; +export * from './date-formats'; diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.html b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.html index cbd7065cfb..ea5b3a9437 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.html +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.html @@ -5,11 +5,11 @@ id="dateInput" type="text" [matDatepicker]="datePicker" - [value]="value" + [(ngModel)]="value" [id]="column.id" [required]="column.required" [disabled]="!column.editable" - (focusout)="onDateChanged($any($event).srcElement)" + (focusout)="onDateChanged($any($event).target.value)" (dateChange)="onDateChanged($event)"> diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.spec.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.spec.ts index 381c7f4e51..54aac3be89 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.spec.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.spec.ts @@ -22,7 +22,6 @@ import { DynamicTableRow } from '../models/dynamic-table-row.model'; import { DynamicTableModel } from '../models/dynamic-table.widget.model'; import { DateEditorComponent } from './date.editor'; import { By } from '@angular/platform-browser'; -import { MatDatepickerInputEvent } from '@angular/material/datepicker'; import { TranslateModule } from '@ngx-translate/core'; describe('DateEditorComponent', () => { @@ -55,7 +54,7 @@ describe('DateEditorComponent', () => { describe('using Date Piker', () => { it('should update row value on change', () => { - const input = {value: '14-03-2016'} as MatDatepickerInputEvent; + const input = {value: '2016-03-14'} as any; component.ngOnInit(); component.onDateChanged(input); @@ -66,7 +65,7 @@ describe('DateEditorComponent', () => { it('should flush value on user input', () => { spyOn(table, 'flushValue').and.callThrough(); - const input = {value: '14-03-2016'} as MatDatepickerInputEvent; + const input = { value: '2016-03-14' } as any; component.ngOnInit(); component.onDateChanged(input); diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.ts index d4e9d3b642..7a90741acf 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/date/date.editor.ts @@ -15,32 +15,29 @@ * limitations under the License. */ -/* eslint-disable @angular-eslint/component-selector */ - -import { UserPreferencesService, UserPreferenceValues, MomentDateAdapter, MOMENT_DATE_FORMATS } from '@alfresco/adf-core'; -import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { ADF_DATE_FORMATS, AdfDateFnsAdapter, DateFnsUtils } from '@alfresco/adf-core'; +import { Component, Input, OnInit } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { MatDatepickerInputEvent } from '@angular/material/datepicker'; -import moment, { Moment } from 'moment'; import { DynamicTableColumn } from '../models/dynamic-table-column.model'; import { DynamicTableRow } from '../models/dynamic-table-row.model'; import { DynamicTableModel } from '../models/dynamic-table.widget.model'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { isValid } from 'date-fns'; @Component({ selector: 'adf-date-editor', templateUrl: './date.editor.html', providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS } + { provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS }, + { provide: DateAdapter, useClass: AdfDateFnsAdapter } ], styleUrls: ['./date.editor.scss'] }) -export class DateEditorComponent implements OnInit, OnDestroy { +export class DateEditorComponent implements OnInit { DATE_FORMAT: string = 'DD-MM-YYYY'; - value: any; + @Input() + value: Date; @Input() table: DynamicTableModel; @@ -51,43 +48,32 @@ export class DateEditorComponent implements OnInit, OnDestroy { @Input() column: DynamicTableColumn; - minDate: Moment; - maxDate: Moment; + minDate: Date; + maxDate: Date; - private onDestroy$ = new Subject(); - - constructor(private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) {} + constructor(private dateAdapter: DateAdapter) {} ngOnInit() { - this.userPreferencesService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe((locale) => this.dateAdapter.setLocale(locale)); + const momentDateAdapter = this.dateAdapter as AdfDateFnsAdapter; + momentDateAdapter.displayFormat = this.DATE_FORMAT; - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.DATE_FORMAT; - - this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT); + this.value = this.table.getCellValue(this.row, this.column) as Date; } - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); - } + onDateChanged(newDateValue: MatDatepickerInputEvent | string) { + if (typeof newDateValue === 'string') { + const newValue = DateFnsUtils.parseDate(newDateValue, this.DATE_FORMAT); - onDateChanged(newDateValue: MatDatepickerInputEvent | HTMLInputElement) { - if (newDateValue?.value) { - /* validates the user inputs */ - const momentDate = moment(newDateValue.value, this.DATE_FORMAT, true); - - if (!momentDate.isValid()) { - this.row.value[this.column.id] = newDateValue.value; - } else { - this.row.value[this.column.id] = `${momentDate.format('YYYY-MM-DD')}T00:00:00.000Z`; + if (isValid(newValue)) { + this.row.value[this.column.id] = `${DateFnsUtils.formatDate(newValue, 'yyyy-MM-dd')}T00:00:00.000Z`; this.table.flushValue(); + } else { + this.row.value[this.column.id] = newDateValue; } - } else { - /* removes the date */ + } else if (newDateValue?.value) { + this.row.value[this.column.id] = `${DateFnsUtils.formatDate(newDateValue?.value, 'yyyy-MM-dd')}T00:00:00.000Z`; + this.table.flushValue(); + } else { this.row.value[this.column.id] = ''; } } diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.html b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.html index 4cbeac7ad0..5377bbb3fe 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.html +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.html @@ -7,7 +7,7 @@ [id]="column.id" [required]="column.required" [disabled]="!column.editable" - (focusout)="onDateChanged($any($event).srcElement.value)" + (focusout)="onDateChanged($any($event).target.value)" (dateChange)="onDateChanged($event)"> { component.column = column; }); - it('should update fow value on change', () => { + it('should update row value on change', () => { component.ngOnInit(); - const newDate = moment('22-6-2018 04:20 AM', 'D-M-YYYY hh:mm A'); - component.onDateChanged(newDate); - expect(moment(row.value[column.id]).isSame(newDate)).toBeTruthy(); + const newDate = new Date('2018-6-22 04:20 AM'); + component.onDateChanged({ value: newDate } as any); + + expect(row.value[column.id]).toBe('22/06/2018 04:20'); }); it('should update row value upon user input', () => { - const input = '22-6-2018 04:20 AM'; + const input = '22/6/2018 04:20'; component.ngOnInit(); component.onDateChanged(input); const actual = row.value[column.id]; - expect(actual).toBe('22-6-2018 04:20 AM'); + expect(actual).toBe('2018-06-22T04:20:00.000Z'); }); it('should flush value on user input', () => { spyOn(table, 'flushValue').and.callThrough(); - const input = '22-6-2018 04:20 AM'; + const input = '22/6/2018 04:20'; component.ngOnInit(); component.onDateChanged(input); diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.ts index 03d1f795de..3406ec58bb 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/datetime/datetime.editor.ts @@ -15,35 +15,31 @@ * limitations under the License. */ -/* eslint-disable @angular-eslint/component-selector */ - -import { MOMENT_DATE_FORMATS, MomentDateAdapter, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; -import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { ADF_DATETIME_FORMATS, ADF_DATE_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, /*MOMENT_DATE_FORMATS, MomentDateAdapter*/ +DateFnsUtils} from '@alfresco/adf-core'; +import { Component, Input, OnInit } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; -import moment, { Moment } from 'moment'; import { DynamicTableColumn } from '../models/dynamic-table-column.model'; import { DynamicTableRow } from '../models/dynamic-table-row.model'; import { DynamicTableModel } from '../models/dynamic-table.widget.model'; -import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; -import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerInputEvent } from '@mat-datetimepicker/core'; @Component({ selector: 'adf-datetime-editor', templateUrl: './datetime.editor.html', providers: [ - { provide: DateAdapter, useClass: MomentDateAdapter }, - { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS }, - { provide: DatetimeAdapter, useClass: MomentDatetimeAdapter }, - { provide: MAT_DATETIME_FORMATS, useValue: MAT_MOMENT_DATETIME_FORMATS } + { provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS }, + { provide: MAT_DATETIME_FORMATS, useValue: ADF_DATETIME_FORMATS }, + { provide: DateAdapter, useClass: AdfDateFnsAdapter }, + { provide: DatetimeAdapter, useClass: AdfDateTimeFnsAdapter } ], styleUrls: ['./datetime.editor.scss'] }) -export class DateTimeEditorComponent implements OnInit, OnDestroy { +export class DateTimeEditorComponent implements OnInit { DATE_TIME_FORMAT: string = 'DD/MM/YYYY HH:mm'; - value: any; + @Input() + value: Date; @Input() table: DynamicTableModel; @@ -54,40 +50,28 @@ export class DateTimeEditorComponent implements OnInit, OnDestroy { @Input() column: DynamicTableColumn; - minDate: Moment; - maxDate: Moment; + minDate: Date; + maxDate: Date; - private onDestroy$ = new Subject(); - - constructor(private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService) {} + constructor(private dateAdapter: DateAdapter) {} ngOnInit() { - this.userPreferencesService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe((locale) => this.dateAdapter.setLocale(locale)); + const momentDateAdapter = this.dateAdapter as AdfDateFnsAdapter; + momentDateAdapter.displayFormat = this.DATE_TIME_FORMAT; - const momentDateAdapter = this.dateAdapter as MomentDateAdapter; - momentDateAdapter.overrideDisplayFormat = this.DATE_TIME_FORMAT; - - this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_TIME_FORMAT); + this.value = this.table.getCellValue(this.row, this.column) as Date; } - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); - } - - onDateChanged(newDateValue) { - if (newDateValue?.value) { - const newValue = moment(newDateValue.value, this.DATE_TIME_FORMAT); - this.row.value[this.column.id] = newDateValue.value.format(this.DATE_TIME_FORMAT); + onDateChanged(newDateValue: MatDatetimepickerInputEvent | string) { + if (typeof newDateValue === 'string') { + const newValue = DateFnsUtils.parseDate(newDateValue, this.DATE_TIME_FORMAT); this.value = newValue; + this.row.value[this.column.id] = newValue.toISOString(); this.table.flushValue(); - } else if (newDateValue) { - const newValue = moment(newDateValue, this.DATE_TIME_FORMAT); - this.value = newValue; - this.row.value[this.column.id] = newDateValue; + } else if (newDateValue.value) { + const newValue = DateFnsUtils.formatDate(newDateValue.value, this.DATE_TIME_FORMAT); + this.row.value[this.column.id] = newValue; + this.value = newDateValue.value; this.table.flushValue(); } else { this.row.value[this.column.id] = ''; diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.spec.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.spec.ts new file mode 100644 index 0000000000..a370f810ff --- /dev/null +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.spec.ts @@ -0,0 +1,94 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DateCellValidator } from './date-cell-validator-model'; +import { DynamicTableColumn } from './dynamic-table-column.model'; +import { DynamicTableRow } from './dynamic-table-row.model'; +import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model'; + +describe('DateCellValidator', () => { + let validator: DateCellValidator; + + beforeEach(() => { + validator = new DateCellValidator(); + }); + + it('should require column to validate', () => { + expect(validator.isSupported(null)).toBeFalse(); + }); + + it('should support only editable columns', () => { + const readonly = { editable: false, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + expect(validator.isSupported(readonly)).toBeFalse(); + + const editable = { editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + expect(validator.isSupported(editable)).toBeTrue(); + }); + + it('should support only date column type', () => { + const date = { editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + expect(validator.isSupported(date)).toBeTrue(); + + const unsupported = { editable: true, type: 'unknown' } as DynamicTableColumn; + expect(validator.isSupported(unsupported)).toBeFalse(); + }); + + it('should skip validating unsupported columns', () => { + const column = { editable: true, type: 'unknown' } as DynamicTableColumn; + const row = {} as DynamicTableRow; + + expect(validator.validate(row, column)).toBeTrue(); + }); + + it('should reject when required column has no value', () => { + const column = { id: 'col1', required: true, editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + const row = { value: { col1: null } } as DynamicTableRow; + + expect(validator.validate(row, column)).toBeFalse(); + }); + + it('should approve when optional column has no value', () => { + const column = { id: 'col1', required: false, editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + const row = { value: { col1: null } } as DynamicTableRow; + + expect(validator.validate(row, column)).toBeTrue(); + }); + + it('should approve the valid datetime value', () => { + const column = { id: 'col1', required: true, editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + const row = { value: { col1: '2023-10-12T10:59:24.773Z' } } as DynamicTableRow; + + expect(validator.validate(row, column)).toBeTrue(); + }); + + it('should reject invalid datetime value', () => { + const column = { id: 'col1', required: true, editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + const row = { value: { col1: '!2023-10-12T10:59:24.773Z' } } as DynamicTableRow; + + expect(validator.validate(row, column)).toBeFalse(); + }); + + it('should update validation summary of rejection', () => { + const column = { id: 'col1', name: 'created_on', required: true, editable: true, type: DateCellValidator.DATE_TYPE } as DynamicTableColumn; + const row = { value: { col1: '!2023-10-12T10:59:24.773Z' } } as DynamicTableRow; + const summary = new DynamicRowValidationSummary(); + + expect(validator.validate(row, column, summary)).toBeFalse(); + expect(summary.isValid).toBeFalse(); + expect(summary.message).toBe(`Invalid 'created_on' format.`); + }); +}); diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.ts index c788cb269b..40266ea6a4 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/date-cell-validator-model.ts @@ -17,34 +17,40 @@ /* eslint-disable @angular-eslint/component-selector */ -import moment from 'moment'; import { CellValidator } from './cell-validator.model'; import { DynamicRowValidationSummary } from './dynamic-row-validation-summary.model'; import { DynamicTableColumn } from './dynamic-table-column.model'; import { DynamicTableRow } from './dynamic-table-row.model'; +import { isValid } from 'date-fns'; export class DateCellValidator implements CellValidator { - private supportedTypes: string[] = ['Date']; + static DATE_TYPE = 'Date'; + + private supportedTypes: string[] = [DateCellValidator.DATE_TYPE]; isSupported(column: DynamicTableColumn): boolean { - return column?.editable && this.supportedTypes.indexOf(column.type) > -1; + return !!(column?.editable && this.supportedTypes.indexOf(column?.type) > -1); } validate(row: DynamicTableRow, column: DynamicTableColumn, summary?: DynamicRowValidationSummary): boolean { if (this.isSupported(column)) { - const value = row.value[column.id]; + const value = row?.value[column.id]; - if (!value && !column.required) { - return true; - } + if (value) { + const dateValue = new Date(value); + + if (isValid(dateValue)) { + return true; + } - const dateValue = moment(value, 'YYYY-MM-DDTHH:mm:ss.SSSSZ', true); - if (!dateValue.isValid()) { if (summary) { summary.isValid = false; summary.message = `Invalid '${column.name}' format.`; } + return false; + } else { + return !column.required; } } diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-row-validation-summary.model.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-row-validation-summary.model.ts index 18bd0bd43a..a31afec798 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-row-validation-summary.model.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-row-validation-summary.model.ts @@ -17,14 +17,12 @@ import { ErrorMessageModel } from '@alfresco/adf-core'; -/* eslint-disable @angular-eslint/component-selector */ - export class DynamicRowValidationSummary extends ErrorMessageModel { - isValid: boolean; constructor(json?: any) { super(json); - this.isValid = json.isValid; + + this.isValid = json?.isValid; } } diff --git a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-table.widget.model.ts b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-table.widget.model.ts index 4f8425d981..36604f326e 100644 --- a/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-table.widget.model.ts +++ b/lib/process-services/src/lib/form/widgets/dynamic-table/editors/models/dynamic-table.widget.model.ts @@ -15,9 +15,6 @@ * limitations under the License. */ -/* eslint-disable @angular-eslint/component-selector */ - -import moment from 'moment'; import { ValidateDynamicTableRowEvent } from '../../../../events/validate-dynamic-table-row.event'; import { FormService, FormFieldModel, FormWidgetModel } from '@alfresco/adf-core'; import { CellValidator } from './cell-validator.model'; @@ -178,7 +175,7 @@ export class DynamicTableModel extends FormWidgetModel { if (column.type === 'Date') { if (rowValue) { - return moment(rowValue.split('T')[0], 'YYYY-MM-DD').format('DD-MM-YYYY'); + return new Date(rowValue.split('T')[0]); } } diff --git a/lib/testing/src/lib/protractor/process-services-cloud/pages/edit-task-filter-cloud-component.page.ts b/lib/testing/src/lib/protractor/process-services-cloud/pages/edit-task-filter-cloud-component.page.ts index 2dde1bf390..b66b29361a 100644 --- a/lib/testing/src/lib/protractor/process-services-cloud/pages/edit-task-filter-cloud-component.page.ts +++ b/lib/testing/src/lib/protractor/process-services-cloud/pages/edit-task-filter-cloud-component.page.ts @@ -16,7 +16,6 @@ */ import { browser, protractor, ElementFinder, $$, $ } from 'protractor'; -import { EditTaskFilterDialogPage } from './dialog/edit-task-filter-dialog.page'; import { BrowserVisibility } from '../../core/utils/browser-visibility'; import { BrowserActions } from '../../core/utils/browser-actions'; import { DropdownPage } from '../../core/pages/material/dropdown.page'; @@ -59,7 +58,6 @@ export class EditTaskFilterCloudComponentPage { dueDateRangeDropdown = new DropdownPage($(`mat-select[data-automation-id='adf-cloud-edit-process-property-dueDateRange']`)); dueDateRangeWithin = new DatePickerPage($(`mat-datepicker-toggle[data-automation-id='adf-cloud-edit-picker-date-range-dueDateRange']`)); - editTaskFilterDialogPage = new EditTaskFilterDialogPage(); peopleCloudComponent = new PeopleCloudComponentPage(); groupCloudComponent = new GroupCloudComponentPage(); @@ -68,10 +66,6 @@ export class EditTaskFilterCloudComponentPage { private expansionPanelExtended = this.rootElement.$('mat-expansion-panel-header.mat-expanded'); private content = this.rootElement.$('div.mat-expansion-panel-content[style*="visible"]'); - editTaskFilterDialog(): EditTaskFilterDialogPage { - return this.editTaskFilterDialogPage; - } - async isFilterDisplayed(): Promise { return BrowserVisibility.waitUntilElementIsVisible(this.filter); }