mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
2.4.0 (#3522)
* [ADF-2914] support for number range patterns (#3282) * support for number range patterns * fix memory leak for tag actions * [ADF-2824] Reviewed docs for core and content services (#3290) * [ADF-2824] Reviewed card view docs * [ADF-2824] Reviewed doc for core and content services * [ADF-2824] Added class name exception to prop script * [ADF-2843] added tooltip for create new folder and edit folder icons … (#3286) * [ADF-2843] added tooltip for create new folder and edit folder icons from Content Service * [ADF-2843] added translation for tooltip * Update content-metadata.component.md * [ADF_NOISSUE] Fix styling and sidenav layout fixes (#3291) * Fix styling and sidenav layout fixes * Revert packagr conflicting mods * [ADF-2905] Added JSDocs for process services (#3292) * [ADF-2905] Added JSDocs for process services * [ADF-2905] Added missing return value in tasklist service * [ADF-2916] number range form validator added (#3279) * number range form validator added * tests added * [ADF-2626] added preserved state functionality for sidenav component (#3278) * [ADF-2626] added preserved state functionality for sidenav component * [ADF-2626] changed logic for preserving the sidenav component state * [ADF-2626] added missing curly brace } * [ADF-2626] small changes on logic based on pr comments * [ADF-2843] added tooltip for create folder and edit folder icons from Content Services * Revert "[ADF-2843] added tooltip for create folder and edit folder icons from Content Services" This reverts commitd5a7abb65b. * [ADF-2626] changed casting * [ADF-2626] updated documentation with event * [ADF-2626] removed app-config pipe because it was not being used * [ADF-2328] filtering support for facets and categories (#3293) * filtering support for facets and categories * fix tests * update variable names * [ADF-2973] Angular pipe to get application configuration settings (#3301) * app config pipe * update docs * [ADF-2849] Search Date Range - Set the format of the date from config (#3288) * format date chosen from the datePicker's calendar * format date on focusout event * fix tests & some code refactoring * more validation messages * unit tests * fix typecast error * move "dateFormat" to be part of the "date range" widget settings block * fix error on Moment "...Type 'moment.Moment' is not assignable to type 'moment.Moment'. Two different types with this name exist, but they are unrelated. Property 'isLocal' is missing in type 'Moment'..." * moment - use old version * change script - use recent version of moment * Added the working-with-nodes-api-service tutorial. (#3295) * [ADF-2826] added a check for duplicate permission add (#3265) * [ADF-2556] first step to create add people or group to permissions * [ADF-2556] creating a dialog with user results * [ADF-2556] integrated service for add and remove permission from node * [ADF-2556] fixed behaviour and style for add user group * [ADF-2556] added some refactoring for dialog service * [ADF-2556] refactoring the dependencies of the components * [ADF-2556] added some fix and a new key for dialog * [ADF-2556] start adding test for node permission service * [ADF-2556] added test for add permission panel component * [ADf-2556] adding tests for new add permission component * [ADF-2556] fixed tests and added documentation * [ADF-2556] fixed documentation for add-node components * [ADF-2556] added peer review changes * [ADF-2826] added a check for duplicate permission add * [ADF-2826] removed fdescribe * [ADF-2826] applied peer review changes * [ADF-2930] general purpose "Empty Page" component (#3296) * empty content component * fix selector name * style fixes * cleanup code * docs for empty content * update docs * update docs * use typography for icon * layout fixes for Login (ported from ACA) * fix aot crash * fix tslint error and add and block if tslint give error in the pipeline * [ADF-2892] Sidenav now prevents closing itself when ESC key is pressed (#3302) * [ADF-2892] Sidenav now closes when clicking outside of it * [ADF-2892] Sidevan now prevents closing itself when ESC key is pressed * Fix for a technical bug, which prevents the proper usage of the expanded output event. (#3304) * [ADF-NO-PRIVATE] renoved private property which breaks AOT (#3305) * [ADF-2975] New input for File Upload Button component (#3299) * new nodeType input * unit tests * content type fix * remove package-lock.json files * [ADF-2699] added localisation for time ago pipe (#3298) * [ADF-2699] added localisation to the time-ago pipe * [ADF-2699] added lang to time ago pipe * [ADF-2699] added localisation for time ago pipe * [ADF-2699] removed fdescribe * [ADF-2699] removed comments * [ADF-2699] removed useless default values * fix css scope for search filter * fix aot error * fix ID show rancher script update * Addition to the preparation of the environment. (#3306) * Added the working-with-nodes-api-service tutorial. * Added the 'Alfresco Example Content Application' paragraph to the 'Preparing the development environment' tutorial. * [ADF-2843] added tooltips for all the buttons in toolbar (#3297) * [ADF-2843] added tooltip for create new folder and edit folder icons from Content Service * [ADF-2843] added translation for tooltip * [ADF-2843] added tooltips for all the buttons in toolbar * [ADF-2843] discard package-lock * [ADF-2832] discard changes to package-lock * [ADF-2843] discarded changes from package-lock * [ADF-2843] added tooltip for list view button * [ADF-2912] added group everyone as constant result for add permissions (#3266) * [ADF-2556] first step to create add people or group to permissions * [ADF-2556] creating a dialog with user results * [ADF-2556] integrated service for add and remove permission from node * [ADF-2556] fixed behaviour and style for add user group * [ADF-2556] added some refactoring for dialog service * [ADF-2556] refactoring the dependencies of the components * [ADF-2556] added some fix and a new key for dialog * [ADF-2556] start adding test for node permission service * [ADF-2556] added test for add permission panel component * [ADf-2556] adding tests for new add permission component * [ADF-2556] fixed tests and added documentation * [ADF-2556] fixed documentation for add-node components * [ADF-2556] added peer review changes * [ADF-2912] added group everyone as constant result for add permissions * [ADF-2912] readded jsdoc * fix search filter styles (#3313) * [ADF-2978] added a check when locally set permission is undefined (#3307) * [ADF-2969] Moved doc tools to new tools folder (#3314) * [ADF-2969] Moved doc tools to new tools folder * [ADF-2969] Added files missing from schematic * [ADF-2969] Added missing files to schematic * [ADF-2813] set default sorting (#3311) * [ADF-2891] decoupled search service love (#3312) * Added the tutorial 'Creating your JavaScript application using alfresco-js-api'. (#3310) * Added the working-with-nodes-api-service tutorial. * Added the 'Alfresco Example Content Application' paragraph to the 'Preparing the development environment' tutorial. * Added the tutorial 'Creating your JavaScript application using alfresco-js-api'. * Added the image of the tutorial. * Typo in 'creating-javascript-app-using-alfresco-js-api.md' Typo in 'Prerequisites'. * Typos in 'creating-javascript-app-using-alfresco-js-api.md' Two small typos. * Wrong sentence in 'creating-javascript-app-using-alfresco-js-api.md' ...into Angular applications, but it is "framework agnostic". * Typo on 'creating-javascript-app-using-alfresco-js-api.md'. ...and Angular... * [ADF-2888] UX doesn't respect the Sidenav specifiactions (#3303) * [ADF-2771] Sidebar action menu component - UX review * Add a input property to set the width of sidebar-action-menu. * [ADF-2888] UX doesn't respect the Sidenav specifiactions * Add a input property to set the width of sidebar-action-menu. * remove search configuration enforcement (#3315) * [ADF-2986] try to pick the start value from local storage before the standard default (#3318) * [ADF-2912] group everyone is always visible even for no search result (#3316) * [ADF-2826] fixed wrong parsing for error message (#3317) * [ADF-2739] Improved breadcrumb logic (#3287) * [ADF-2739] Long names in breadcrumb fixed * [ADF-2739] Updated styles * [ADF-2739] Fixing @mixin for breadcrumb * [ADF-2739] Waiting for changes in demo-shell * [ADF-2739] Fixed @mixin * [ADF-2739] Fixed issue related to breadcrumb position * [ADF-2739] Improved ngOnChanges call for breadcrumb * [ADF-2739] Fixed issues with lint * [ADF-2739] Removed comment in dropdown breadcrumb component file * [ADF-2739] Changed recalculateNodes method from public to protected * pipeline-update (#3309) * deploy PR script * [ADF-2967] fixed create new task navigation (#3322) * [ADF-2967] fixed create new task navigation * [ADF-2967] removed extra variables * [ADF-2967] removed unused methods * [ADF-2961] support for protocol substition in app config files (#3324) * support for protocol substition in app config files * Update app-config.service.spec.ts * Update app-config.service.spec.ts * [ADF-2925] Required and invalid validators added (#3277) * test added * tests clean * test added * tslint clean * test name changed * update tests * Added the tutorial 'Working with the Nodes API Service'. (#3319) * [ADF-2979] Updated props script to avoid copying missing JSDocs (#3327) * [ADF-2979] Updated tools to ignore blank JSDocs for inputs/outputs * [ADF-2979] Bug fixes for handling missing tables, etc * [ADF-2989] updated documentation with multiple node example (#3328) * [ADF-2503] conditional visibility for content actions (#3325) * conditional visibility for content actions * fix typo * workaround for "target: all" * [ADF-2821] move module sidebar action menu (#3321) * move module sidebar action menu * fix core import test * remove sidebar module * skip error * skiperror old versions * [ADF-2995] Permissions - Consumer should be able to upload a new version for his file on a private site (#3326) * check node permission instead of parent * tests * Fix sidenav action icons (#3331) * Only the username form control should be emitted (#3333) * about component (#3337) * [ADF-2563] Improve versioning functionality (#3335) * change input with textarea * update file version use now the update content API * provide way to test read only mode version list * fix test * test fix * [ADF-2997] The meaning of the range fields is not clear (#3338) * [ADF-2997] restrict 'to' field not to allow dates in the future * [ADF-2997] more clear labels for start and end date fields the translation team will decide what will be the best values for the date fields * [ADF-3000] Update the documentation by adding the steps of date format custom configuration. (#3339) * [ADF-3028] i18n support for title service (#3342) * i18n support for title service * cleanup tests * update tests * fixed constraint for viewer name width (#3343) * (demo shell) login css fixes (#3344) * [ADF-1997] override custom form widgets by default (#3346) * override custom form widgets by default * update tests * [ADF-2979] Updated doc tools to avoid erasing MD docs with blank JSDocs in services (#3347) * [ADF-2979] Update to ignore blank JSDocs for method signature * [ADF-2979] Finished adding blank JSDoc check for services * [ADF-2131] Search sorting (#3334) * sorting configuration * detect primary sorting and use with document list * search results sorting * docs update * unit tests and code updates * update code * update code * generic sorting picker, test updates * ability to switch off client side sorting * update docs for document list * [ADF-2884] Task List - Custom apps display all tasks (#3329) * [ADF-2884] Handled filterParam input change Changed component selector * [ADF-2884] Modified/Added tests * [ADF-2884] Deprecated old selectors * [ADF-2884] Added deprecated version * [ADF-2884] Improved filter finding condition * update project permissions change * [ADF-2984] Show date invalid message on search date range picker (#3323) * [ADF-2984] Show date invalid message on search date range picker * [ADF-2984] test that required format is displayed when date input is invalid * [ADF-2984] More space above buttons * dummy commit * restore deleted breadcrumb code * [ADF-2753] New error component (#3336) * [ADF-1938] Overflowing text in reports section fidex * [ADF-1938] Long names in report section now fit * [ADF-1938] Reverted changes in container widget * [ADF-2753] New error component created * [ADF-2753] Unit test for Error Content Component * Deleting unused files * Deleting unused files * Deleting unused files * [ADF-2753] Documentation added * [ADF-2753] Fixed minor bugs * [ADF-2753] Authentication not needed to view error * add error handler * tslint fix * router app component * remove unused import * fix import modules * limit to 404 * destroy fixture after any test * misspelling error * breadcrumb bug fixes (#3348) * [ADF-2726] fixed save content for external repository (#3341) * [ADF-2726] start fixing the show of files loaded from CS * [ADF-2726] start fixing the show of files loaded from CS * [ADF-2726] fixed save content for external repository| * [ADF-2726] fixed save content for external repository| * [ADF-2726] reeanabled and fixed the tests * [ADF-2726] reeanabled and fixed the tests * [ADF-2726] added tests for attach file widget and activiti alfresco service * [ADF-2726] added tests for attach file widget and activiti alfresco service * [ADF-2726] fixed test * fix like test * fix typo in the resource key (#3352) * fix failing tests replace ajax with spy * remove fdescribe * [ADF-3040] Replaced doc template engine to fix whitespace issues (#3356) * replace jasmine ajax with spy in rating tests * fix breadcrumb align in object picker (#3353) * fix no content node display (#3351) * Broken links and missing images for two tutorials. (#3357) * Broken links and missing images for two tutorials. * Replaced .html with .md extensions Replaced .html with .md extensions. * Typo in Prerequi(si)tes Typo in Prerequi(si)tes. * [ADF-2988] fix logo change (#3362) * [ADF-3055] fix bug with page size for facet fields (#3359) * fix bug with page size for facet fields * move page size to a constant * move const inside the scope * test fixes * [ADF-2923] button type added (#3358) * button type added * remove unnecessary code * [ADF-2608] added a fix to handle selection on slow connection problem (#3350) * [ADF-2608] added a fix to handle selection on slow connection problem * [ADF-2608] fixed test for datatable * Added an import command to the 'Adding a new view' tutorial. (#3366) * remove redundant logic (#3361) * [ADF-2679] Reviewed latest tutorial content (#3371) * [ADF-2679] Initial review of new tutorial content * [ADF-2679] Reviewed the latest tutorials * [ADF-2679] Initial review of new tutorial content * [ADF-2679] Reviewed the latest tutorials * [ADF-2832] Demo-shell, Fix Task filter dropdown menu alignment (#3294) * [ADF-2832] changed height to auto for task filter dropdown header * [ADF-2832] change selector with classes * [ADF-2832] changed selector * [ADF-2832] removed unnecessary css * [ADF-2832] added class and changed selector * [ADF-2832] added adf-prefix * [ADF-2927] search filter enhancements (#3365) * search filter enhancements * reset button for facet queries * update code and tests * remove unused type * restore code missing after rebase * [ADF-2764] Updated doc files with latest script features (#3368) * [ADF-2764] Updated doc files with latest script features * [ADF-2764] Rebuilt full index instead of just content services index * [ADF-2985] Demo shell - Provide an easy way to test the security issue (#3354) * Provide an easy way to test the security issue * use logService * first part random test fix (#3376) fixing random test executions first part * fix form radio test * [ADF-3065] TaskList component - Deprecate the processDefinitionKey (#3369) * Deprecate the processDefinitionKey property. remove unused tasklist code. It was needed with the old pagination component * Remove nosense test * Use adf version * [ADF-2999] readded form validation icon (#3367) * [ADF-2999] readded form validation icon * [ADF-2999] fixed broken test * Added the tutorial 'Basic theming'. (#3378) * [ADF-2854] Fix demoshell task/process filter and routing (#3360) * Fix demoshell task/process filter and routing * Use the correct js api model * Fix unit test * search filter now remembers original user query (#3384) * [ADF-2319] added a check for delete checklist button (#3382) * [ADF-2238] removed created by property (#3380) * [ADF-2238] removed created by property * [ADF-2238] added a condition to avoid checklist delete for completed task * Revert "[ADF-2238] added a condition to avoid checklist delete for completed task" This reverts commitfc2227ea04. * bold font for checked boxes (#3381) * [ADF-2774] fixed message bus, background and language dropdowns that were hidden by the toolbar (#3379) * [ADF-2764] Applied new doc tool features to core library (#3383) * [ADF-2824] Added and reviewed some content services docs (#3385) * revert changes (#3387) * Added the 'Content metadata component' tutorial. (#3386) * update package settings and lock file (#3389) * minor layout fix for search facet buttons * [ADF-2857] fixed message for date and date time widget (#3370) * [ADF-2764] Updated process services docs with latest features (#3390) * [ADF-2977] made language change persistenr (#3373) * [ADF-3041] TaskList Component - Empty State issue. (#3345) * [DW-635] Empty State Component * [DW-635] Empty State Component * [DW-635] Empty state Issue * [DW-635] Use empty state component and custom empty directive * [ADF-3041] Documentation for TaskList Component - Empty State issue. * [ADF-3088] move sorting picker to a separate module (#3396) * move sorting picker to a separate module * update translation mock * [ADF-3087] Clarified section about adding/replacing keys in i18n guide (#3392) * [ADF-3087] Clarified section about adding/replacing keys * [ADF-3087] Corrected information in i18n and translation service docs * [ADF-3068] ADF Sidenav/Toolbar - Ux background colour review (#3401) * [ADF-2824] Reviewed docs for new components in 2.4 (#3400) * [ADF-3042] Use the custom date adapter from adf-core on Search Date Range widget (#3394) -fix localization -fix tests -show parse error * [ADF-3053] breadcrumb fixes (#3406) * translate breadcrumb root in demo shell * optional "max items" feature and style fixes * should not be restricted by default * style updates * toolbar and breadcrumb layout fixes * breadcrumb demo and testing page * full toolbar scenario * fix translation issue with the dropdown and custom root * a11y fixes * fix issue with duplicate id, remove unused attribute * [ADF-2627] Icons-only mode for Info Drawer (#3398) * tab with icon * docs * test * add option without login docker publish * fix publish script * move docker push outside login * fix random test failing part 2 (#3395) * fix random failing test core search/comment/auth/user * fix node delete directive * fix lint issues * node restore fix * fix comment test * remove fdescribe * fix tests and tslint * fix upload test * unsubscribe success event task test * copy comment object during test * use the data pipe performance improvement and standard usage * uncomment random test * fix comment date random failing test * disposable unsubscribe * fix start process * remove fdescribe * change start process test and remove commented code * fix error event check double click * clone object form test * refactor date time test * fix service mock * fix test dropdown and context * git hook lint * fix language test * unsubscribe documentlist event test * fix disposable error * fix console log service error document list * unusbscribe search test * clear input field * remove wrong test * [ADF-2754] People Widget - Refactoring style and logic (#3349) * observable logic added * tests added & bug fixed * onkeyup removed * isValidUser method removed * tslint fix * test added * comments fixed * check if input value is string * remove console log err from document list test * [ADF-3082] Task Filter - Custom Task filters don't work (#3402) * [ADF-3082] Custom Task filters don't work. * Added an sorting input to the datatable. * Updated documentation for the recent changes. * Added testcases for the recent changes. * [ADF-3082] Custom Task filters don't work * Added a sorting input to the datatable * Added testcases to the recent changes. * Updated doc for the recent changes. * * Refactored task/process list dataSort. * * Refactored process/task list datasort method * remove console log error from version list and content node selector test stubbing necessary method * [ADF-3041] - Task List Empty State Issue (#3408) * fix demo shell gallery view switch (#3413) * [ADF-2541] reset datatable selection when rows are changed from code (#3410) * reset selection when rows are replaced from code * code fixes * unit test updates * visualise selection count for testing purposes * make row selection api public * remove question mark from the event name * remove pdfviewer thumbnail console log test error * fix conflict link creation id (#3412) * [ADF-2753] Error Component throwing default error fixed (#3364) * [ADF-1938] Overflowing text in reports section fidex * [ADF-1938] Long names in report section now fit * [ADF-1938] Reverted changes in container widget * [ADF-2753] New error component created * [ADF-2753] Unit test for Error Content Component * Deleting unused files * Deleting unused files * Deleting unused files * [ADF-2753] Documentation added * [ADF-2753] Error Component throwing default error fixed * [ADF-2753] White space removed * [ADF-2753] Removed unnecessary files and updated trnaslation file * [ADF-2753] Changed link for button in error component * [ADF-2753] Updated doc file * [ADF-2753] Removed fdescribe * [ADF-2753] Improved code coverage * fix error AppTitle service test log * fix gallery view demo shell route * [ADF-3095] ability to intercept, pause and resume upload process (#3416) * prevent and resume upload process * upload fixes and confirmation dialog demo * ability to toggle the upload confirmation demo * fix tests * unit tests * docs update * remove deprecation * fix test name * [ADF-2698] Date and time Widget - UTC vs local time issue (#3393) * [ADF-2698] Date and time Widget - UTC vs local time issue * [ADF-2698] add unit test to check that the format for min and max values is correct * [ADF-2698] test behavior * [ADF-3102] added lazy loading for tab content to fix animations (#3418) * [ADF-2764] Fixed union type display glitches in property tables (#3419) * [ADF-2764] Test commit to fix handling of union types in Github docs * [ADF-2764] Fixed union type error in component docs * [ADF-2593] z-index fix for search bar (#3409) * [ADF-2593] * [ADF-2593] Removed unnecessary style attributes * [ADF-3111] pagination is not showed when automation test are running (#3420) * [ADF-3108] Search - facetQueries panel renders when not defined in configuration (#3417) * show facetQueries based on configuration definition * tests * FacetQueries is defined * [ADF-3039] Task List - Enanchement (#3404) * * Created DataColumnSchemaAssembler component to get column schema from html and app.config.json * Removed column related method from tasklist. * * Removed data property from the tasklist component * Using rows input property instead of data input property of the datatable * * Renamed DataColumnSchemaAssembler to DataTableSchema * Refactored DataTableSchema component * * Changed schem property into an input schemaColumns property in dataTable component * * Added selectFirstRow input property to select a first row of datatable * Removed unnecessary method from tasklist component * * Added test case for the recent changes * Added mock object for the tasklist spec * * Added testcases for recent changes in the datatable component * * Updated datatable and tasklist document for the recent changes * * Refactored process-service and task list component * Updated datatable document. * [ADF-3039] Task List - Enanchement * Changed schemaColumn name to columns * Updated datatable documentation. * data input Annotated with @deprecated in the tasklist component * * Added an sorting input to the datatable. * Updated datatable and tasklist documentation * Added method to get current sorting order. * * After rebasing * * Revert sorting changes * * After rebase * * fixed conflicts * * Fixed failing testcase after rebased. * update toolbar docs (#3423) * Add custom data holder for CardViewItems (#3422) * [ADF-3115] a11y fixes (#3424) * a11y fixes for search input * a11y fixes for pagination * a11y fixes for content actions * [ADF-3118] translation support for notification service (snackbars) (#3427) * translation support for snackbar * unit tests * [ADF-2438] fixed thumbnails height (#3407) * [ADF-2438] calculate thumbnail height * [ADF-2438] send height to parent element * [ADF-2438] add width to css * [ADF-2438] moved height and width logic to parent component * [ADF-2438] added height and width logic to parent component * [ADF-2438] fixed failing test * demo translation for search radio * [ADF-2710] removed default shared link creation at the opening (#3430) * [ADF-2999] added a way to test the validation icon on demo shell (#3431) * [ADF-2754] People component tests fixed and style improvement (#3414) * tests fixed and style * show people test fixed * error message test fixed * [ADF-3131] fix selection order for DT/DL (#3433) * fix selection order for DT/DL * remove fit * [ADF-3066] ProcessList Component - Empty State issue. (#3434) * [ADF-3093] Added style checking tool for en.json translation file (#3428) * [ADF-3093] Started li18nt VS Code extension * [ADF-3093] Started work on UI style lint tool for VSCode * [ADF-3093] Added UI style rules up to sg0006 * [ADF-3093] Added remaining style rules * [ADF-3093] Added docs and command line tool * [ADF-3093] Removed Microsoft notices and updated licences to Apache-2.0 * [ADF-3133] Fixed inconsistency in doc example (#3436) * [ADF-2753] Fixed routing and second button showing up (#3421) * [ADF-1938] Overflowing text in reports section fidex * [ADF-1938] Long names in report section now fit * [ADF-1938] Reverted changes in container widget * [ADF-2753] New error component created * [ADF-2753] Unit test for Error Content Component * Deleting unused files * Deleting unused files * Deleting unused files * [ADF-2753] Documentation added * [ADF-2753] Removed unnecessary files and updated trnaslation file * [] * [ADF-2753] Fixed routing and second button showing up * [ADF-2753] Fixed typo * [ADF-2753] Fixed view loading before variables * [ADF-2753] Missing whitespace * [ADF-2753] Added test for route params * [ADF-2753] Changed getData function name to getTranslations * [ADF-fix date widget test] Fixing a randomly failing test (#3439) * group uploaded files into single batch, IE fixes (#3438) * [ADF-3067] App-list Empty State. (#3432) * extend demo shell upload example to button * [ADF-2764] Added new type linker features and applied them to core docs (#3442) * [ADF-2764] Added basic support for composite and external types * [ADF-2764] Added new type linker features and applied to core docs * notification service demo and testing area (#3443) * notification service demo and testing area * fix typo * fix viewer title style (#3445) * [ADF-3132] TaskList empty state doesn't respect the specification (#3440) * [ADF-3134] fix increasing search calls issue (#3444) * [ADF-2764] Applied new type linker features to content services docs (#3446) * [ADF-3062] dual api support for Favorites (#3447) * dual api support for Favorites * unit test * ADF-2974] New Buttons Menu component version (#3429) * [ADF-2974] Buttons injected from parent html component * [ADF-2974] New version of buttons menu component * [ADF-2974] Updated unit tests * [ADF-2974] Updated documentation * [ADF-2974] Removed unused variable * [ADF-2974] Fixed failing test at analytics report parameters * [ADF-2974] Removed fdescribe * [ADF-2974] Moved mock inside testing file for buttons menu component * extend breadcrumb testing page (demo shell) * [ADF-3142] Added missing info about 'remember me' in login docs (#3448) * [ADF-3120] fixed sorting for tasklist and process list (#3435) * [ADF-3120] fixed sorting for tasklist and process list * [ADF-3120] removed commented code * [ADF-3120] fixing another randomly failing test * [ADF-2764] Applied new type linker features to proc services docs (#3449) * (demo shell) fix memory leak with search results * [ADF-2795] SSO implicitflow (#3332) * Enable OAUTH2 * Create SSO services * SSO improvements * Rollback sso login change * Add SSO configuration from Setting component * Refactoring * Remove login ECM/BPM toggle and move use the userpreference instead of store * fix host setting unit test * Fix unit test missing instance * use the Js api oauth * add logout component and clean sso not used class * fix dependencies cicle * add translation settings * fix style setting page * clean * JS APi should receive the oauth config from the userPreference and not from the config file * change login if SSO is present * missing spaces * add sso test in login component * add logout directive new properties test * Improve host setting and remove library reference * fix login test * Remove unused code * Fix authentication unit test * fix authguard unit test * fix csrf check login component * fix unit test core and demo shell * remove * simple filtering for datatable (#3453) * [ADF-3150] Moved all doc tool config settings to doctool.config.json (#3455) * [ADF-3150] Moved undoc stoplist to doc config file * [ADF-3150] Moved config to doctools.config.json and removed obsolete scripts * fixes for memory leaks and multiple subscriptions (#3456) * fix content action memory leaks * memory leak fixes for demo shell * [no-issue] error image resolver mimetype should not be part of datatable (#3415) * move error image custom logic form datatable to documentlist * change travis * [fix-test-log] added optional function to data row interface * [no-issue] fixed datatable tests * [no-issue] fixing tests * [ADF-2859] conditional evaluation of disabled state for content actions (#3450) * react on [disabled] binding changes * [disabled] binding updates for context menu items * evaluating disabled state with a function * unit test * restore original description * remove irrelevant test * fix tests * fix test * remove not used events in demo shell host setting components * [ADF-3156] App config default ALL (BPM and ECM) provider (#3460) * Use as default provider ALL (BPM and ECM) * Default host OAUTH * removing wrong fdescribe (#3461) * [ADF-3123] removed default choice for the first radio button (#3462) * Fix host component (#3463) * [ADF-2824] Doc review and minor changes to review checker tool (#3466) * restore historical selection order (#3467) * restore historical selection order * fix test * [ADF-2859] fixes for the conditional visibility and disabled states (#3465) * fixes for the conditional visibility and disabled states * update docs * cleanup code * remove unused code * [LOC-61] i18n 2.4.0 (#3468) * i18n 2.4.0 * missing part * revert development config * [ADF-3144] Error component displayed when closing file viewer in process tab (#3469) * [ADF-3173] [Task List] - Empty list message is not translated (#3471) * Added a missing translation keys. * Fix dropdown panel in viewer toolbar because of z-index (#3472) * [ADF-3162] Setting component - Should be able to render the providers passed as input (#3464) * Be able to change the available providers values * Add deprecated tag * add default providers * Fix empty OAuth and selected providers Improve the documentation * Fix BPM guard to check SSO condition * Add the oauthConfig again * SSO or Basic otpion auth setting change * fix host settings sso/basic add login documentation * remove test string * fix auth guard test * dispose upload emitter test events * remove space * exclude failing test * Update ADF packages version 2.4.0-beta13 (#3475) * [ADF-2824] Reviewed search docs for 2.4 (#3477) * [ADF-2824] Moved search widget docs into separate pages * [ADF-2824] Separated search widget page and added search filter service docs * [ADF-2824] Updated new search docs with doc tools * Remove unused field (#3480) * Fix default SSO config not filled in (#3479) * [ADF-3175] renabling upload on single target folder row (#3473) * [ADF-3191] Not able to login only to BPM or ECM (#3478) * disable random test * host setting component reset JS-API * reload api after user prefrence change * remove replicate reset * missing semicolon * deprecate provider property * remove trailing space * check new value before to set * [ADF-3198] Updated index files + minor doc updates (#3482) * [ADF-3200] custom Extension filter - file with different extension is uploaded with drag and drop * [ADF-3181] Auth Guard - redirect by string url (#3474) * redirect by string url * auth guard tests fix * revise * [ADF-3196] fixed task selection and double click (#3484) * [ADF-3191] Fix userprefrence oauth2 (#3488) * remove user preference setting save * fix host setting test * remove userPreferences test * fix title service test * remove unused imports * restore input align * fix service import in test tag rating * [ADF-3210] Typo People Search (#3487) * remove vscode changes * [ADF-3196] [Task list / Process list] - The 'Task details' are not diplayed for the selected Task (#3483) * [ADF-3196] [Task list / Process list] - The 'Task details' are not displayed for the selected Task * * After rebase * [ADF-3176] Fixed permissionsissue when editing metadata (#3476) * [ADF-3217] reset previous search settings (#3490) * reset previous search settings * unit test * remove fit * [ADF-3196] fixed selection of first tasklist element (#3491) * [ADF-3196] [Task list / Process list] - The 'Task details' are not displayed for the selected Task * * After rebase * [ADF-3196] fixed selection for fist element * [ADF-3212] Upload version buttons wrapping fixed (#3492) * Update ADF packages version 2.4.0-beta14 (#3493) * demo shell host setting component login redirect after setting * remove old auth test not valid anymore * fix translations (#3497) * [ADF-3203] Fixed doc tools bugs from Linux run and added verbose option (#3496) * [ADF-3912] Setting Component - Return as default BASIC authType (#3495) * Return as default BASIC authType * remove the commented line * [ADF-3190] Fix rerendering issue with content-meta-data (#3494) * Fix rerendering issue with content-meta-data * Fix indentation * [ADF-3198] Index and tutorial content review (#3500) * [ADF-3198] Fixed glitches in doc index files and tools * [ADF-3198] Reviewed new tutorial content and fixed index glitches * [ADF-3198] Fixed host settings component brief description * [ADF-3223] Restore message directive files fix (#3499) * fix restore notification * improve path specification property * fix lint and document issues * fix test * remove unused import * [ADF-3226] remove Double behaviour setting allow download and comment (#3502) * fix restore notification * improve path specification property * fix lint and document issues * fix test * remove unused import * remove double behavior allowComments and allowDonload can be set only thorough input property * [ADF-3233] fixed demo shell filtering for files on drag&drop] (#3504) * [ADF-3232] fixing retrieving for ecm user via site service (#3506) * [ADF-3176] file content reload fixed (#3501) * file content reload fixed * refactoring & layout fix * resolve indentation problems * missing semicolon * [ADF-3224] move ticket store logic in js-api (#3505) * move ticket store logic in js-api * remove space * fix test * fix tests * [ADF-3212] Fixed permission issues when updating version (#3503) * [ADF-3213] Fixed permission issues when updating version * [ADF-3212] fixed test for PR * allowableOperations include viewer get node * update demo shell with metadata demo * undo ADF-3176 changes * fix login oauth cookie problem (#3508) * fix login don't check the cookie in oauth2 * fix test * check boolean storage * fix provider check * clean local storage before auth test * fix missing pdf viewer update when scale is the same (#3510) * [ADF-3239] Fixed deprecated property descriptions (#3509) * [ADF-3239] Changed deprecated properties to include full description * [ADF-3239] Updated deprecated properties of affected doc files * [ADF-3239] Fixed badly formatted doc comment in tasklist * [ADF-3229] people widget list reseted if empty input (#3507) * list reseted when input is empty * test added * [ADF-3242] removed filter for string in example log component (#3511) * [ADF-3242] removed filter for string in example log component * [ADF-3242] added try catch for circular dependency objects * [ADF-3242] added try catch and removed error message * invalidate session in host setting internal component instead to logout * remove unused import * use i18n key for "Permission" label * [ADF-3198] Final doc checks for release (#3513) * [ADF-3198] Doc link check for release * [ADF-3198] Updated main index doc page * remove invalidation session because is concern of the js-api * check 401 and redirect demo shell (#3516) * [ADF-3248] fixed readonly status for people widget (#3512) * [ADF-3248] fixed readonly status for people widget * [ADF-3248] fixed test people widget * [ADF-3248] added readonly test * [ADF-3251] Authentication Service - Check if used loggedin based on the provider (#3515) * check if loggedin based on the provider * Fix unit tests * remove 404 check in demo shell * [ADF-3262] Remove avoidable css style from task/process list component (#3518) * [ADF-3230] Changes to stop doc tools from updating files unnecessarily (#3519) * [ADF-3230] Added basic change detector to doc tools * [ADF-3230] Updates to type linker to fix before/after inconsistencies * [ADF-3230] Fixed comparison error caused by adjacent text blocks * [ADF-3230] Added basic change detector to doc tools * [ADF-3230] Updates to type linker to fix before/after inconsistencies * [ADF-3230] Fixed comparison error caused by adjacent text blocks * [ADF-3230] Modified props tool to remove spaces from union types * [ADF-3230] Made ToC tool before/after state consistent * [ADF-3265] Updated index script to remove brief desc links (#3520) * bump 2.4.0 version
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<div class="about-container">
|
||||
<h3>Server settings</h3>
|
||||
<mat-list>
|
||||
<small>The values below are taken from the AppConfigService </small>
|
||||
<mat-list-item>
|
||||
<h4 matLine> Alfresco Process Services URL: {{ bpmHost }}</h4>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item>
|
||||
<h4 matLine>Alfresco Content Services URL: {{ ecmHost }}</h4>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
||||
<h3>Product Versions</h3>
|
||||
<div *ngIf="bpmVersion">
|
||||
<h3>BPM</h3>
|
||||
<label> Edition </label> {{ bpmVersion.edition }}
|
||||
<p></p>
|
||||
<label> Version </label> {{ bpmVersion.majorVersion }}.{{ bpmVersion.minorVersion }}.{{ bpmVersion.revisionVersion }}
|
||||
</div>
|
||||
<div *ngIf="ecmVersion">
|
||||
<h3>ECM</h3>
|
||||
<label> Edition </label> {{ ecmVersion.edition }}
|
||||
<p></p>
|
||||
<label> Version </label> {{ ecmVersion.version.display }}
|
||||
<p></p>
|
||||
<h4>License</h4>
|
||||
<adf-datatable [data]="license"></adf-datatable>
|
||||
|
||||
<h4> Status</h4>
|
||||
<adf-datatable [data]="status"></adf-datatable>
|
||||
|
||||
<h4>Modules</h4>
|
||||
|
||||
<adf-datatable [data]="modules"></adf-datatable>
|
||||
</div>
|
||||
|
||||
<div *ngIf="githubUrlCommitAlpha">
|
||||
<h3>Source code</h3>
|
||||
<small>You are running the project based on the following commit:</small>
|
||||
<div>
|
||||
<a [href]="githubUrlCommitAlpha">{{githubUrlCommitAlpha}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Packages</h3>
|
||||
<small>Current project is using the following ADF libraries:</small>
|
||||
<adf-datatable [data]="data"></adf-datatable>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
.about-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.adf-table-version {
|
||||
width: 60%;
|
||||
border: 0;
|
||||
border-spacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
import { BpmProductVersionModel, EcmProductVersionModel } from '../models/product-version.model';
|
||||
import { DiscoveryApiService } from '../services/discovery-api.service';
|
||||
import { ObjectDataTableAdapter } from '../datatable/data/object-datatable-adapter';
|
||||
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-about',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AboutComponent implements OnInit {
|
||||
|
||||
data: ObjectDataTableAdapter;
|
||||
status: ObjectDataTableAdapter;
|
||||
license: ObjectDataTableAdapter;
|
||||
modules: ObjectDataTableAdapter;
|
||||
githubUrlCommitAlpha = 'https://github.com/Alfresco/alfresco-ng2-components/commits/';
|
||||
|
||||
ecmHost = '';
|
||||
bpmHost = '';
|
||||
|
||||
ecmVersion: EcmProductVersionModel = null;
|
||||
bpmVersion: BpmProductVersionModel = null;
|
||||
|
||||
constructor(private http: Http,
|
||||
private appConfig: AppConfigService,
|
||||
private authService: AuthenticationService,
|
||||
private discovery: DiscoveryApiService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
if (this.authService.isEcmLoggedIn()) {
|
||||
this.discovery.getEcmProductInfo().subscribe((ecmVers) => {
|
||||
this.ecmVersion = ecmVers;
|
||||
|
||||
this.modules = new ObjectDataTableAdapter(this.ecmVersion.modules, [
|
||||
{ type: 'text', key: 'id', title: 'ID', sortable: true },
|
||||
{ type: 'text', key: 'title', title: 'Title', sortable: true },
|
||||
{ type: 'text', key: 'version', title: 'Description', sortable: true },
|
||||
{ type: 'text', key: 'installDate', title: 'Install Date', sortable: true },
|
||||
{ type: 'text', key: 'installState', title: 'Install State', sortable: true },
|
||||
{ type: 'text', key: 'versionMin', title: 'Version Minor', sortable: true },
|
||||
{ type: 'text', key: 'versionMax', title: 'Version Max', sortable: true }
|
||||
]);
|
||||
|
||||
this.status = new ObjectDataTableAdapter([this.ecmVersion.status], [
|
||||
{ type: 'text', key: 'isReadOnly', title: 'ReadOnly', sortable: true },
|
||||
{ type: 'text', key: 'isAuditEnabled', title: 'Is Audit Enable', sortable: true },
|
||||
{ type: 'text', key: 'isQuickShareEnabled', title: 'Is quick shared enable', sortable: true },
|
||||
{ type: 'text', key: 'isThumbnailGenerationEnabled', title: 'Thumbnail Generation', sortable: true }
|
||||
]);
|
||||
|
||||
this.license = new ObjectDataTableAdapter([this.ecmVersion.license], [
|
||||
{ type: 'text', key: 'issuedAt', title: 'Issued At', sortable: true },
|
||||
{ type: 'text', key: 'expiresAt', title: 'Expires At', sortable: true },
|
||||
{ type: 'text', key: 'remainingDays', title: 'Remaining Days', sortable: true },
|
||||
{ type: 'text', key: 'holder', title: 'Holder', sortable: true },
|
||||
{ type: 'text', key: 'mode', title: 'Mode', sortable: true },
|
||||
{ type: 'text', key: 'isClusterEnabled', title: 'Is Cluster Enabled', sortable: true },
|
||||
{ type: 'text', key: 'isCryptodocEnabled', title: 'Is Cryptodoc Enable', sortable: true }
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.authService.isBpmLoggedIn()) {
|
||||
this.discovery.getBpmProductInfo().subscribe((bpmVers) => {
|
||||
this.bpmVersion = bpmVers;
|
||||
});
|
||||
}
|
||||
|
||||
this.http.get('/versions.json?' + new Date()).subscribe(response => {
|
||||
const regexp = new RegExp('^(@alfresco)');
|
||||
|
||||
const alfrescoPackages = Object.keys(response.json().dependencies).filter((val) => {
|
||||
return regexp.test(val);
|
||||
});
|
||||
|
||||
const alfrescoPackagesTableRepresentation = [];
|
||||
alfrescoPackages.forEach((val) => {
|
||||
alfrescoPackagesTableRepresentation.push({
|
||||
name: val,
|
||||
version: response.json().dependencies[val].version
|
||||
});
|
||||
});
|
||||
|
||||
this.gitHubLinkCreation(alfrescoPackagesTableRepresentation);
|
||||
|
||||
this.data = new ObjectDataTableAdapter(alfrescoPackagesTableRepresentation, [
|
||||
{ type: 'text', key: 'name', title: 'Name', sortable: true },
|
||||
{ type: 'text', key: 'version', title: 'Version', sortable: true }
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
this.ecmHost = this.appConfig.get<string>(AppConfigValues.ECMHOST);
|
||||
this.bpmHost = this.appConfig.get<string>(AppConfigValues.BPMHOST);
|
||||
}
|
||||
|
||||
private gitHubLinkCreation(alfrescoPackagesTableRepresentation): void {
|
||||
const corePackage = alfrescoPackagesTableRepresentation.find((packageUp) => {
|
||||
return packageUp.name === '@alfresco/adf-core';
|
||||
});
|
||||
|
||||
if (corePackage) {
|
||||
const commitIsh = corePackage.version.split('-');
|
||||
if (commitIsh.length > 1) {
|
||||
this.githubUrlCommitAlpha = this.githubUrlCommitAlpha + commitIsh[1];
|
||||
} else {
|
||||
this.githubUrlCommitAlpha = this.githubUrlCommitAlpha + corePackage.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { DataTableModule } from '../datatable/datatable.module';
|
||||
import { DataColumnModule } from '../data-column/data-column.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
TranslateModule,
|
||||
DataTableModule,
|
||||
DataColumnModule
|
||||
],
|
||||
declarations: [
|
||||
AboutComponent
|
||||
],
|
||||
exports: [
|
||||
AboutComponent
|
||||
]
|
||||
})
|
||||
export class AboutModule {}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './about.component';
|
||||
|
||||
export * from './about.module';
|
||||
@@ -18,13 +18,20 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AppConfigService } from './app-config.service';
|
||||
import { AppConfigPipe } from './app-config.pipe';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
HttpClientModule
|
||||
],
|
||||
declarations: [
|
||||
AppConfigPipe
|
||||
],
|
||||
providers: [
|
||||
AppConfigService
|
||||
],
|
||||
exports: [
|
||||
AppConfigPipe
|
||||
]
|
||||
})
|
||||
export class AppConfigModule {
|
||||
|
||||
+11
-20
@@ -15,25 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService } from './alfresco-api.service';
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { AppConfigService } from './app-config.service';
|
||||
|
||||
describe('AlfrescoApiService', () => {
|
||||
@Pipe({
|
||||
name: 'adfAppConfig'
|
||||
})
|
||||
export class AppConfigPipe implements PipeTransform {
|
||||
constructor(private config: AppConfigService) {}
|
||||
|
||||
let service: AlfrescoApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new AlfrescoApiService(null, null);
|
||||
});
|
||||
|
||||
it('should rase nodeChanged event with node payload', (done) => {
|
||||
const node: any = {};
|
||||
|
||||
service.nodeUpdated.subscribe(result => {
|
||||
expect(result).toEqual(node);
|
||||
done();
|
||||
});
|
||||
|
||||
service.nodeUpdated.next(node);
|
||||
});
|
||||
|
||||
});
|
||||
transform(value: string, fallback: any): any {
|
||||
return this.config.get(value, fallback);
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,18 @@ describe('AppConfigService', () => {
|
||||
expect(appConfigService.get('testUrl')).toBe('http://localhost:9090');
|
||||
});
|
||||
|
||||
it('should use protocol value', () => {
|
||||
spyOn(appConfigService, 'getLocationPort').and.returnValue('9090');
|
||||
const protocolSpy = spyOn(appConfigService, 'getLocationProtocol');
|
||||
appConfigService.config.testUrl = '{protocol}//{hostname}:{port}';
|
||||
|
||||
protocolSpy.and.returnValue('https:');
|
||||
expect(appConfigService.get('testUrl')).toBe('https://localhost:9090');
|
||||
|
||||
protocolSpy.and.returnValue('ftp:');
|
||||
expect(appConfigService.get('testUrl')).toBe('ftp://localhost:9090');
|
||||
});
|
||||
|
||||
it('should load external settings', () => {
|
||||
appConfigService.load().then(config => {
|
||||
expect(config).toEqual(mockResponse);
|
||||
|
||||
@@ -21,11 +21,24 @@ import { ObjectUtils } from '../utils/object-utils';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export enum AppConfigValues {
|
||||
APP_CONFIG_LANGUAGES_KEY = 'languages',
|
||||
PROVIDERS = 'providers',
|
||||
OAUTHCONFIG = 'oauth2',
|
||||
ECMHOST = 'ecmHost',
|
||||
BPMHOST = 'bpmHost',
|
||||
AUTHTYPE = 'authType',
|
||||
CONTEXTROOTECM = 'contextRootEcm',
|
||||
CONTEXTROOTBPM = 'contextRootBpm',
|
||||
ALFRESCO_REPOSITORY_NAME = 'alfrescoRepositoryName',
|
||||
LOG_LEVEL = 'logLevel',
|
||||
LOGIN_ROUTE = 'loginRoute',
|
||||
DISABLECSRF = 'disableCSRF'
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AppConfigService {
|
||||
|
||||
static APP_CONFIG_LANGUAGES_KEY = 'languages';
|
||||
|
||||
config: any = {
|
||||
application: {
|
||||
name: 'Alfresco ADF Application'
|
||||
@@ -44,10 +57,21 @@ export class AppConfigService {
|
||||
this.onLoad = this.onLoadSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests notification of a property value when it is loaded.
|
||||
* @param property The desired property value
|
||||
* @returns Property value, when loaded
|
||||
*/
|
||||
select(property: string): Observable<any> {
|
||||
return this.onLoadSubject.map((config) => config[property]).distinctUntilChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a named property.
|
||||
* @param key Name of the property
|
||||
* @param defaultValue Value to return if the key is not found
|
||||
* @returns Value of the property
|
||||
*/
|
||||
get<T>(key: string, defaultValue?: T): T {
|
||||
let result: any = ObjectUtils.getValue(this.config, key);
|
||||
if (typeof result === 'string') {
|
||||
@@ -55,6 +79,7 @@ export class AppConfigService {
|
||||
map.set('hostname', this.getLocationHostname());
|
||||
map.set(':port', this.getLocationPort(':'));
|
||||
map.set('port', this.getLocationPort());
|
||||
map.set('protocol', this.getLocationProtocol());
|
||||
result = this.formatString(result, map);
|
||||
}
|
||||
if (result === undefined) {
|
||||
@@ -63,14 +88,34 @@ export class AppConfigService {
|
||||
return <T> result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location.protocol value.
|
||||
*/
|
||||
getLocationProtocol(): string {
|
||||
return location.protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location.hostname property.
|
||||
* @returns Value of the property
|
||||
*/
|
||||
getLocationHostname(): string {
|
||||
return location.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location.port property.
|
||||
* @param prefix Text added before port value
|
||||
* @returns Port with prefix
|
||||
*/
|
||||
getLocationPort(prefix: string = ''): string {
|
||||
return location.port ? prefix + location.port : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the config file.
|
||||
* @returns Notification when loading is complete
|
||||
*/
|
||||
load(): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
this.http.get('app.config.json').subscribe(
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { StorageService } from '../services/storage.service';
|
||||
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class DebugAppConfigService extends AppConfigService {
|
||||
constructor(private storage: StorageService, http: HttpClient) {
|
||||
super(http);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
get<T>(key: string, defaultValue?: T): T {
|
||||
if (key === AppConfigValues.OAUTHCONFIG) {
|
||||
return <T> (JSON.parse(this.storage.getItem(key)) || super.get<T>(key, defaultValue));
|
||||
} else {
|
||||
return <T> (<any> this.storage.getItem(key) || super.get<T>(key, defaultValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,7 @@
|
||||
*/
|
||||
|
||||
export * from './app-config.service';
|
||||
export * from './debug-app-config.service';
|
||||
export * from './app-config.pipe';
|
||||
|
||||
export * from './app-config.module';
|
||||
|
||||
@@ -273,10 +273,19 @@
|
||||
"description": "BPM host",
|
||||
"type": "string"
|
||||
},
|
||||
"providers": {
|
||||
"description": "This parameter can accept as value ECM BPM or ALL you can set it accordingly where you want log-in",
|
||||
"type": "string",
|
||||
"enum": [ "ECM", "BPM", "ALL"]
|
||||
},
|
||||
"contextRootBpm": {
|
||||
"description": "The context root of the BPM host",
|
||||
"type": "string"
|
||||
},
|
||||
"disableCSRF": {
|
||||
"description": "The context root of the BPM host",
|
||||
"type": "boolean"
|
||||
},
|
||||
"application": {
|
||||
"description": "Application's global configuration",
|
||||
"type": "object",
|
||||
@@ -365,7 +374,7 @@
|
||||
"properties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": [ "assignee", "status", "priority", "dueDate", "category", "parentName", "created-by", "created", "id", "description", "formName" ]
|
||||
"enum": [ "assignee", "status", "priority", "dueDate", "category", "parentName", "created", "id", "description", "formName" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,23 +467,189 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"authType": {
|
||||
"description": "Kind of authentication BASIC or OAUTH, default value BASIC",
|
||||
"type": "string",
|
||||
"enum": [ "BASIC", "OAUTH" ]
|
||||
},
|
||||
"oauth2": {
|
||||
"description": "AUTH configuration parameters",
|
||||
"type": "object",
|
||||
"required": [ "host", "clientId", "secret" ],
|
||||
"required": [ "host", "clientId", "secret", "scope" ],
|
||||
"properties": {
|
||||
"host": { "type": "string" },
|
||||
"silentLogin": { "type": "boolean" },
|
||||
"authPath": { "type": "string" },
|
||||
"clientId": { "type": "string" },
|
||||
"secret": { "type": "string" }
|
||||
"secret": { "type": "string" },
|
||||
"redirectUri": { "type": "string" },
|
||||
"redirectUriLogout": { "type": "string" },
|
||||
"scope": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"adf-version-manager": {
|
||||
"description": "Configuration parameters for Version Manager component",
|
||||
"search": {
|
||||
"description": "Search configuration parameters",
|
||||
"type": "object",
|
||||
"required": [ "categories" ],
|
||||
"properties": {
|
||||
"allowComments": { "type": "boolean" },
|
||||
"allowDownload": { "type": "boolean" }
|
||||
"include": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filterQueries": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [ "query" ],
|
||||
"properties": {
|
||||
"query": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"facetFields": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [ "field", "mincount", "label" ],
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"description": "This specifies the facet field."
|
||||
},
|
||||
"mincount": {
|
||||
"type": "number",
|
||||
"description": "This specifies the minimum count required for a facet field to be included in the response. The default value is 1."
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "This specifies the label to include in place of the facet field."
|
||||
},
|
||||
"prefix": {
|
||||
"type": "string",
|
||||
"description": "This restricts the possible constraints to only indexed values with a specified prefix."
|
||||
},
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of results"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "number",
|
||||
"description": "Display page size"
|
||||
},
|
||||
"offset": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"facetQueries": {
|
||||
"type": "object",
|
||||
"required": ["label", "queries"],
|
||||
"properties": {
|
||||
"label": {
|
||||
"description": "Category label text",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "Default page size of the category",
|
||||
"type": "number"
|
||||
},
|
||||
"expanded": {
|
||||
"description": "Toggles expanded state of the category",
|
||||
"type": "boolean"
|
||||
},
|
||||
"queries": {
|
||||
"description": "List of custom facet queries",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [ "query", "label" ],
|
||||
"properties": {
|
||||
"query": { "type": "string" },
|
||||
"label": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [ "id", "name" ],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"enabled": { "type": "boolean" },
|
||||
"expanded": { "type": "boolean" },
|
||||
"component": {
|
||||
"type": "object",
|
||||
"required": [ "selector", "settings" ],
|
||||
"properties": {
|
||||
"selector": {
|
||||
"description": "Unique component runtime identifier",
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"description": "Component-specific settings",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorting": {
|
||||
"description": "Sorting options and defaults",
|
||||
"required": ["options"],
|
||||
"properties": {
|
||||
"options": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"description": "Sorting options available for users to choose from",
|
||||
"type": "object",
|
||||
"required": ["key", "label", "type", "field", "ascending"],
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"label": { "type": "string" },
|
||||
"type": { "type": "string" },
|
||||
"field": { "type": "string" },
|
||||
"ascending": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"description": "Predefined sorting to execute by default",
|
||||
"options": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["key", "label", "type", "field", "ascending"],
|
||||
"properties": {
|
||||
"key": { "type": "string" },
|
||||
"label": { "type": "string" },
|
||||
"type": { "type": "string" },
|
||||
"field": { "type": "string" },
|
||||
"ascending": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,182 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 187 43" style="enable-background:new 0 0 187 43;" width="187" height="43" xml:space="preserve">
|
||||
viewBox="80 0 912.8 328.2" style="enable-background:new 0 0 912.8 328.2;" xml:space="preserve" width="180" height="58">
|
||||
<style type="text/css">
|
||||
.st0{fill:#77777A;}
|
||||
.st1{fill:none;}
|
||||
.st2{fill:#77BC1F;}
|
||||
.st3{fill:#F38B00;}
|
||||
.st4{fill:#0071CE;}
|
||||
.st5{fill:#002F87;}
|
||||
.st6{fill:#F9BE00;}
|
||||
.st7{fill:#00AF41;}
|
||||
.st0{fill:#7A7E82;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#8CC03F;}
|
||||
.st3{fill:#F99F39;}
|
||||
.st4{fill:#0D81C5;}
|
||||
.st5{fill:#045FA6;}
|
||||
.st6{fill:#FDDF4F;}
|
||||
.st7{fill:#4AA749;}
|
||||
.st8{fill:none;stroke:#7A7E82;stroke-width:0.75;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M59.7,4.4h3.2l11.9,30.2h-3.1L68,25.2H54.2l-3.7,9.4h-3.1L59.7,4.4z M55.1,22.7H67L61.1,7.2L55.1,22.7z"/>
|
||||
<rect x="75.6" y="4.4" class="st0" width="2.7" height="30.2"/>
|
||||
<path class="st0" d="M90.4,15h-4.3v19.6h-2.7V15h-3.7v-2.2h3.7v-2c0-3.8,1-6.4,5.3-6.4c0.8,0,1.4,0,2.2,0.2v2.3
|
||||
c-0.7-0.1-1.3-0.2-1.9-0.2c-3,0-2.9,1.9-2.9,4.2v1.9h4.3V15z"/>
|
||||
<path class="st0" d="M91.4,12.7h2.5v5.1H94c1.4-3.5,4.3-5.6,8.3-5.4v2.7c-4.8-0.3-8.2,3.3-8.2,7.8v11.6h-2.7V12.7z"/>
|
||||
<path class="st0" d="M104.2,24.4c0.1,3.9,2.1,8.6,7.2,8.6c3.9,0,6-2.3,6.9-5.6h2.7c-1.1,4.9-4,7.8-9.6,7.8c-7,0-9.9-5.3-9.9-11.6
|
||||
c0-5.8,2.9-11.6,9.9-11.6c7.1,0,9.9,6.1,9.6,12.3H104.2z M118.4,22.1c-0.1-4-2.6-7.8-7-7.8c-4.4,0-6.8,3.8-7.2,7.8H118.4z"/>
|
||||
<path class="st0" d="M136.3,19.1c-0.1-3.3-2.7-4.8-5.7-4.8c-2.4,0-5.2,0.9-5.2,3.8c0,2.4,2.7,3.2,4.6,3.7l3.6,0.8
|
||||
c3,0.5,6.2,2.2,6.2,6c0,4.7-4.7,6.6-8.8,6.6c-5.1,0-8.6-2.4-9-7.7h2.7c0.2,3.6,2.9,5.4,6.5,5.4c2.5,0,6-1.1,6-4.1
|
||||
c0-2.5-2.4-3.4-4.8-4l-3.4-0.8c-3.5-0.9-6.1-2.1-6.1-5.8c0-4.4,4.4-6.1,8.2-6.1c4.4,0,7.9,2.3,8,7H136.3z"/>
|
||||
<path class="st0" d="M157.6,19.6c-0.7-3.3-2.7-5.2-6.2-5.2c-5.1,0-7.7,4.6-7.7,9.3c0,4.7,2.6,9.3,7.7,9.3c3.3,0,6-2.6,6.4-6.2h2.7
|
||||
c-0.7,5.2-4.2,8.5-9.1,8.5c-6.7,0-10.4-5.3-10.4-11.6c0-6.3,3.7-11.6,10.4-11.6c4.7,0,8.3,2.5,8.9,7.5H157.6z"/>
|
||||
<path class="st0" d="M182.3,23.7c0,6.3-3.6,11.6-10.4,11.6s-10.4-5.3-10.4-11.6c0-6.3,3.6-11.6,10.4-11.6S182.3,17.4,182.3,23.7
|
||||
M164.3,23.7c0,4.7,2.6,9.3,7.7,9.3c5.2,0,7.7-4.7,7.7-9.3c0-4.7-2.5-9.3-7.7-9.3C166.8,14.4,164.3,19,164.3,23.7"/>
|
||||
<polygon class="st0" points="181.4,11.7 183.8,11.7 183.8,12 182.8,12 182.8,14.8 182.5,14.8 182.5,12 181.4,12 "/>
|
||||
<polygon class="st0" points="184,11.7 184.4,11.7 185.5,14.4 186.6,11.7 187,11.7 187,14.8 186.7,14.8 186.7,12.1 186.7,12.1
|
||||
185.6,14.8 185.3,14.8 184.3,12.1 184.3,14.8 184,14.8 "/>
|
||||
<path class="st1" d="M21.6,21.5v8.6l0,0.3c0,4.9-4,8.9-9,8.9c-4.9,0-9-4-9-8.9c0-0.5,0-1.1,0.1-1.6c3.5,2.4,8.3,2.1,11.4-1
|
||||
L21.6,21.5L21.6,21.5L21.6,21.5z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M15.3,27.8l5.2-5.2h-7.8c-4.3,0-7.8,3.5-7.8,7.8c0,4.3,3.5,7.8,7.8,7.8c0.3,0,0.7,0,1-0.1
|
||||
C11.9,34.8,12.5,30.6,15.3,27.8z"/>
|
||||
<path class="st0" d="M273.5,293.6h5.3v-23.2c0-0.9-0.5-1.4-1.4-1.4h-4.3v-3.5h6c2.6,0,3.8,1.1,3.8,3.5v1.8c0,1.3-0.3,2.3-0.3,2.3
|
||||
h0.1c1.6-4.3,6.7-8.3,11.7-8.3c5.7,0,8.7,2.7,9.6,8h0.1c1.8-4.2,6.3-8,11.7-8c7.6,0,10.4,4.5,10.4,12.2v16.6h5.3v3.5h-9.6v-19
|
||||
c0-5.1-1.1-9.4-6.8-9.4c-6.4,0-10.6,6.8-10.6,13.3v11.6h5.3v3.5h-9.6v-19c0-4.8-0.6-9.4-6.7-9.4c-6.5,0-10.8,6.8-10.8,13.4v11.5
|
||||
h5.3v3.5h-14.9V293.6z"/>
|
||||
<path class="st0" d="M354.6,277.8h2.1v-1.3c0-5.9-3.2-8-7.6-8c-5.3,0-9.2,3.1-9.2,3.1l-2-3.2c0,0,4.3-3.6,11.4-3.6
|
||||
c7.7,0,11.6,4.1,11.6,11.9v15.6c0,0.9,0.5,1.4,1.4,1.4h3.5v3.5h-5.1c-2.7,0-3.8-1.2-3.8-3.5v-0.6c0-1.5,0.2-2.4,0.2-2.4h-0.1
|
||||
c0,0-2.7,7.2-10.8,7.2c-5.1,0-10.5-3-10.5-9.4C335.6,278.2,349.2,277.8,354.6,277.8z M346.7,294.4c6.3,0,9.9-6.3,9.9-11.9V281
|
||||
h-1.8c-2.6,0-14.9-0.3-14.9,7C340,291.3,342.3,294.4,346.7,294.4z"/>
|
||||
<path class="st0" d="M369.3,293.6h5.3v-37.1H369V253h9.9v24.9h5l7.5-8.8h-4.5v-3.5h13.6v3.5H396l-8.7,10v0.1
|
||||
c0.1,0,0.9,0.4,2.2,2.5l6.3,10.5c0.6,1.1,1.1,1.3,2.9,1.3h2.1v3.5h-3.5c-3.1,0-4-0.6-5.5-3.1l-6.7-11.4c-0.8-1.1-1.6-1.3-3.1-1.3
|
||||
h-3.1v12.3h5v3.5h-14.6V293.6z"/>
|
||||
<path class="st0" d="M420.2,264.8c8.7,0,13.2,6.7,13.2,14.6c0,0.8-0.2,2-0.2,2H409c0.2,8.1,5.7,12.7,12.2,12.7
|
||||
c5.8,0,9.6-3.8,9.6-3.8l1.9,3.2c0,0-4.5,4.4-11.7,4.4c-9.4,0-16.5-6.8-16.5-16.5C404.6,271,411.6,264.8,420.2,264.8z M429,278.2
|
||||
c-0.3-6.7-4.3-9.9-8.9-9.9c-5.3,0-9.9,3.5-10.9,9.9H429z"/>
|
||||
<path class="st0" d="M457.7,256.5H452V253h9.9v15.6c0,1.5-0.2,2.6-0.2,2.6h0.1c0,0,2.8-6.4,10.8-6.4c8.5,0,13.7,6.8,13.7,16.6
|
||||
c0,10-6,16.5-14.2,16.5c-7.6,0-10.4-6.5-10.4-6.5h-0.1c0,0,0.2,1.1,0.2,2.6v3.1h-4.1V256.5z M471.8,294.1
|
||||
c5.6,0,10.2-4.6,10.2-12.7c0-7.7-4.1-12.7-10-12.7c-5.3,0-10.2,3.8-10.2,12.7C461.8,287.8,465.1,294.1,471.8,294.1z"/>
|
||||
<path class="st0" d="M496.1,269.1h-5.7v-3.5h9.9v19c0,5,0.8,9.4,6.9,9.4c7.1,0,11.6-6.5,11.6-13.4v-11.5h-5.6v-3.5h9.9v26.7
|
||||
c0,0.9,0.5,1.4,1.4,1.4h3.6v3.5h-5.3c-2.6,0-3.8-1.1-3.8-3.5v-1.6c0-1.3,0.1-2.3,0.1-2.3H519c-0.8,2.4-4.7,8.1-12.5,8.1
|
||||
c-7.3,0-10.4-4-10.4-12.2V269.1z"/>
|
||||
<path class="st0" d="M533.1,287.3h3.9v2.3c0,3.2,4.6,4.6,8.2,4.6c4,0,7.4-1.8,7.4-5c0-3.6-4-4.9-8.5-6.6
|
||||
c-4.8-1.8-9.8-3.9-9.8-9.4c0-5.8,5.7-8.4,10.9-8.4c4.3,0,10.5,1.8,10.5,6.2v3.6h-3.9v-2.3c0-2.3-3.1-3.8-6.5-3.8
|
||||
c-3.5,0-6.7,1.5-6.7,4.6c0,3.5,3.8,4.9,8,6.4c4.9,1.8,10.3,3.8,10.3,9.5c0,5.5-4.9,8.9-11.9,8.9c-5.8,0-11.9-2.6-11.9-7.5V287.3z
|
||||
"/>
|
||||
<path class="st0" d="M562.7,293.6h5.3v-24.5h-5.7v-3.5h9.9v28.1h5.3v3.5h-14.9V293.6z M567.8,253h4.8v5.3h-4.8V253z"/>
|
||||
<path class="st0" d="M582.4,293.6h5.3v-23.2c0-0.9-0.5-1.4-1.4-1.4h-4.3v-3.5h6c2.6,0,3.8,1.1,3.8,3.5v1.8c0,1.3-0.2,2.3-0.2,2.3
|
||||
h0.1c0.9-2.4,5-8.3,12.9-8.3c7.8,0,10.5,4.5,10.5,12.2v16.6h5.3v3.5h-9.6v-19c0-5-0.8-9.4-6.9-9.4c-7,0-12,6.2-12,13.1v11.7h5.3
|
||||
v3.5h-14.9V293.6z"/>
|
||||
<path class="st0" d="M640.3,264.8c8.7,0,13.2,6.7,13.2,14.6c0,0.8-0.2,2-0.2,2h-24.2c0.2,8.1,5.7,12.7,12.2,12.7
|
||||
c5.8,0,9.6-3.8,9.6-3.8l1.9,3.2c0,0-4.5,4.4-11.7,4.4c-9.4,0-16.5-6.8-16.5-16.5C624.8,271,631.8,264.8,640.3,264.8z
|
||||
M649.1,278.2c-0.3-6.7-4.3-9.9-8.9-9.9c-5.3,0-9.9,3.5-10.9,9.9H649.1z"/>
|
||||
<path class="st0" d="M659.3,287.3h3.9v2.3c0,3.2,4.6,4.6,8.2,4.6c4,0,7.4-1.8,7.4-5c0-3.6-4-4.9-8.5-6.6
|
||||
c-4.8-1.8-9.8-3.9-9.8-9.4c0-5.8,5.6-8.4,10.9-8.4c4.3,0,10.5,1.8,10.5,6.2v3.6H678v-2.3c0-2.3-3.1-3.8-6.5-3.8
|
||||
c-3.5,0-6.7,1.5-6.7,4.6c0,3.5,3.8,4.9,8,6.4c4.9,1.8,10.3,3.8,10.3,9.5c0,5.5-4.9,8.9-11.9,8.9c-5.8,0-11.9-2.6-11.9-7.5V287.3z
|
||||
"/>
|
||||
<path class="st0" d="M688.8,287.3h3.9v2.3c0,3.2,4.6,4.6,8.2,4.6c4,0,7.4-1.8,7.4-5c0-3.6-4-4.9-8.5-6.6
|
||||
c-4.8-1.8-9.8-3.9-9.8-9.4c0-5.8,5.7-8.4,10.9-8.4c4.3,0,10.5,1.8,10.5,6.2v3.6h-3.9v-2.3c0-2.3-3.1-3.8-6.5-3.8
|
||||
c-3.5,0-6.7,1.5-6.7,4.6c0,3.5,3.8,4.9,8,6.4c4.9,1.8,10.3,3.8,10.3,9.5c0,5.5-4.9,8.9-11.9,8.9c-5.8,0-11.9-2.6-11.9-7.5V287.3z
|
||||
"/>
|
||||
<path class="st0" d="M793.5,264.8c9.2,0,16.7,7,16.7,16.4c0,9.5-7.5,16.7-16.7,16.7c-9.2,0-16.6-7.2-16.6-16.7
|
||||
C776.9,271.8,784.3,264.8,793.5,264.8z M793.5,294.1c6.8,0,12.3-5.5,12.3-12.9c0-7.2-5.5-12.6-12.3-12.6
|
||||
c-6.7,0-12.2,5.4-12.2,12.6C781.3,288.5,786.8,294.1,793.5,294.1z"/>
|
||||
<path class="st0" d="M813.3,265.6h13.1v3.5h-4.7l6.6,21.2c0.4,1.1,0.5,2.4,0.5,2.4h0.1c0,0,0.2-1.3,0.5-2.4l7.9-24.6h4.2
|
||||
l7.9,24.6c0.3,1.1,0.5,2.4,0.5,2.4h0.1c0,0,0.1-1.3,0.4-2.4l6.7-21.2h-4.7v-3.5h13.1v3.5h-4.1l-9,28.1h-4.6l-7.8-23.7
|
||||
c-0.4-1.1-0.6-2.5-0.6-2.5h-0.2c0,0-0.2,1.4-0.6,2.5l-7.8,23.7h-4.6l-9-28.1h-4V265.6z"/>
|
||||
<path class="st0" d="M752.7,255.7c0.9,0,1.7,0,2.3,0.1v-3.6c-0.7,0-1.5-0.1-2.3-0.1c-6.1,0-13.4,2.6-13.4,12.4v1.1h-5.6v3.5h5.6
|
||||
v24.5H734v3.5h15.9v-3.5h-6.3v-24.5h8v-3.5h-8v-1.1C743.7,257.6,748.4,255.7,752.7,255.7z"/>
|
||||
<path class="st0" d="M772.4,294.1c-3.3,0-3.8-2.2-3.8-4.8v-31h0v-2.3h0v-0.5h0v-3.4h-9.9v3.4h5.7v2.8v1.4v29.9
|
||||
c0,6.2,3,8.2,7.2,8.2c1.2,0,2.4-0.2,2.4-0.2V294C773.9,294,773.2,294.1,772.4,294.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M873,265.5v0.7h-1.3v3.3h-0.8v-3.3h-1.3v-0.7H873z M877.3,269.5l-0.1-2.3c0-0.3,0-0.7,0-1.1h0
|
||||
c-0.1,0.4-0.2,0.8-0.3,1.2l-0.7,2.2h-0.8l-0.7-2.3c-0.1-0.3-0.2-0.8-0.3-1.1h0c0,0.4,0,0.7,0,1.1l-0.1,2.3h-0.8l0.3-4h1.2l0.7,2
|
||||
c0.1,0.3,0.2,0.6,0.3,1h0c0.1-0.4,0.2-0.7,0.3-1l0.7-2h1.1l0.3,4H877.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M21.6,30.4v-7.3l-5.5,5.5l0,0c-3.1,3-3.1,8,0,11c3.1,3,8,3,11.1,0c0.2-0.2,0.4-0.5,0.6-0.7
|
||||
C24.2,37.7,21.6,34.3,21.6,30.4z"/>
|
||||
<path class="st0" d="M272.8,214.5h13.9l16.4-42.6h61.8l16.5,42.6h13.9L342,77.4h-14.5L272.8,214.5L272.8,214.5z M334.2,90.5
|
||||
l26.2,70.3H307L334.2,90.5L334.2,90.5z"/>
|
||||
<polygon class="st0" points="399.9,214.5 411.9,214.5 411.9,77.4 399.9,77.4 399.9,214.5 "/>
|
||||
<path class="st0" d="M463.2,115.4h-19.4v-8.6c0-2.7,0.1-5.2,0.2-7.5c0.1-2.3,0.6-4.3,1.4-6.1c0.8-1.7,2.1-3.1,3.9-4.1
|
||||
c1.8-1,4.3-1.5,7.6-1.5c1.4,0,2.8,0.1,4.2,0.3c1.4,0.2,2.9,0.4,4.6,0.7V78.2c-1.9-0.3-3.6-0.5-5.1-0.6c-1.5-0.1-3.2-0.2-4.9-0.2
|
||||
c-4.8,0-8.8,0.7-11.9,2.1c-3.1,1.4-5.6,3.4-7.3,5.9c-1.8,2.6-3,5.6-3.6,9.2c-0.6,3.6-1,7.5-1,11.7v9h-16.7v10.2h16.7v88.9h12
|
||||
v-88.9h19.4V115.4L463.2,115.4z"/>
|
||||
<path class="st0" d="M467,214.5h12v-52c0-5,0.9-9.8,2.7-14.2c1.8-4.4,4.3-8.2,7.5-11.3c3.2-3.1,7.1-5.6,11.5-7.3
|
||||
c4.4-1.7,9.4-2.4,14.8-2.2v-12.1c-8.9-0.4-16.5,1.4-22.9,5.3c-6.4,3.9-11.1,9.9-14.2,18H478v-23.2h-11V214.5L467,214.5z"/>
|
||||
<path class="st0" d="M596.7,168.2c0.3-6.5-0.4-13-1.9-19.4c-1.5-6.4-4-12-7.5-16.9c-3.5-4.9-8-8.9-13.6-11.9
|
||||
c-5.6-3-12.3-4.5-20.2-4.5c-7.9,0-14.6,1.4-20.2,4.3c-5.6,2.8-10.1,6.6-13.7,11.3c-3.5,4.7-6.2,9.9-7.9,15.9
|
||||
c-1.7,5.9-2.6,12-2.6,18.1c0,6.7,0.9,13,2.6,19c1.7,6,4.4,11.2,7.9,15.8c3.5,4.5,8.1,8.1,13.7,10.8c5.6,2.7,12.3,4,20.2,4
|
||||
c12.4,0,22-3.1,28.8-9.3c6.8-6.2,11.4-14.9,14-26.2h-12c-1.9,7.6-5.3,13.7-10.3,18.3c-4.9,4.7-11.8,7-20.5,7
|
||||
c-5.7,0-10.6-1.1-14.7-3.4c-4.1-2.3-7.5-5.1-10-8.5c-2.5-3.4-4.4-7.3-5.7-11.6c-1.3-4.3-1.9-8.5-1.9-12.6H596.7L596.7,168.2z
|
||||
M521.2,158.1c0.5-4.1,1.5-8.1,3.1-12.1c1.6-4,3.7-7.4,6.4-10.4c2.7-3,5.9-5.4,9.7-7.2c3.8-1.8,8.2-2.7,13.1-2.7
|
||||
c4.8,0,9.1,0.9,12.9,2.7c3.8,1.8,7,4.2,9.7,7.2c2.7,3,4.7,6.4,6.2,10.3c1.5,3.9,2.3,7.9,2.4,12.2H521.2L521.2,158.1z"/>
|
||||
<path class="st0" d="M676.1,144.4c-0.3-5.4-1.3-10-3.2-14c-1.9-4-4.5-7.3-7.7-10c-3.2-2.7-7-4.7-11.2-6
|
||||
c-4.2-1.3-8.8-1.9-13.8-1.9c-4.3,0-8.7,0.5-13,1.5c-4.4,1-8.3,2.6-11.9,4.8c-3.6,2.2-6.4,5.1-8.7,8.6c-2.2,3.6-3.3,7.9-3.3,12.9
|
||||
c0,4.2,0.7,7.8,2.1,10.7c1.4,2.9,3.3,5.3,5.8,7.3c2.5,2,5.4,3.6,8.7,5c3.3,1.3,6.9,2.5,10.8,3.5l15.4,3.5
|
||||
c2.7,0.6,5.3,1.4,7.9,2.3c2.6,0.9,4.9,2,6.9,3.4c2,1.3,3.6,3,4.8,5.1c1.2,2,1.8,4.5,1.8,7.3c0,3.5-0.9,6.4-2.6,8.7
|
||||
c-1.7,2.4-3.9,4.3-6.6,5.9c-2.7,1.5-5.6,2.6-8.8,3.3c-3.2,0.7-6.2,1-8.9,1c-8,0-14.7-2.1-20.1-6.2c-5.4-4.2-8.3-10.3-8.9-18.5
|
||||
h-12c1,12.2,5.1,21,12.3,26.6c7.2,5.6,16.5,8.3,28.1,8.3c4.6,0,9.2-0.5,13.9-1.6c4.7-1.1,8.9-2.8,12.6-5.2
|
||||
c3.7-2.4,6.8-5.4,9.2-9.2c2.4-3.8,3.6-8.3,3.6-13.7c0-4.4-0.8-8.1-2.5-11.3c-1.6-3.2-3.8-5.9-6.4-8.1c-2.6-2.2-5.6-3.9-8.9-5.3
|
||||
c-3.4-1.3-6.8-2.3-10.2-2.8l-16-3.7c-2-0.5-4.2-1.2-6.7-2c-2.4-0.8-4.6-1.9-6.7-3.2c-2-1.3-3.7-2.8-5-4.7c-1.3-1.9-2-4.1-2-6.8
|
||||
c0-3.2,0.7-5.9,2.1-8.1c1.4-2.2,3.2-3.9,5.5-5.3c2.3-1.3,4.8-2.3,7.5-2.9c2.7-0.6,5.4-0.9,8.1-0.9c3.4,0,6.7,0.4,9.7,1.2
|
||||
c3,0.8,5.7,2.1,8.1,3.9c2.3,1.8,4.2,4.1,5.6,6.8c1.4,2.7,2.1,6,2.3,9.7H676.1L676.1,144.4z"/>
|
||||
<path class="st0" d="M766.6,149.4c-1.4-11.4-5.7-19.9-13-25.5c-7.3-5.6-16.2-8.4-26.7-8.4c-7.5,0-14.1,1.3-19.9,4
|
||||
c-5.8,2.7-10.6,6.2-14.6,10.7c-3.9,4.5-6.9,9.7-8.9,15.8c-2,6-3,12.4-3,19.1c0,6.8,1,13.1,3,19.1c2,6,5,11.2,8.9,15.7
|
||||
c3.9,4.5,8.8,8,14.6,10.7c5.8,2.7,12.4,4,19.9,4c11,0,20.1-3.4,27.3-10.2c7.2-6.8,11.6-16.2,13.2-28.2h-12
|
||||
c-0.4,4.1-1.4,7.9-3,11.3c-1.7,3.5-3.7,6.4-6.3,8.9c-2.5,2.5-5.5,4.5-8.8,5.9c-3.3,1.4-6.8,2.1-10.5,2.1c-5.7,0-10.7-1.1-15-3.4
|
||||
c-4.3-2.3-7.9-5.3-10.7-9c-2.9-3.7-5-7.9-6.5-12.6c-1.5-4.7-2.2-9.5-2.2-14.4c0-4.9,0.7-9.7,2.2-14.4c1.5-4.7,3.6-8.9,6.5-12.6
|
||||
c2.9-3.7,6.4-6.7,10.7-8.9c4.3-2.3,9.3-3.4,15-3.4c7.9,0,14,2.1,18.5,6.3c4.5,4.2,7.6,10.1,9.2,17.5H766.6L766.6,149.4z"/>
|
||||
<path class="st0" d="M818,115.4c-7.5,0-14.1,1.3-19.9,4c-5.8,2.7-10.6,6.2-14.5,10.7c-3.9,4.5-6.9,9.7-8.9,15.8
|
||||
c-2,6-3,12.4-3,19.1c0,6.8,1,13.1,3,19.1c2,6,5,11.2,8.9,15.7c3.9,4.5,8.8,8,14.5,10.7c5.8,2.7,12.4,4,19.9,4
|
||||
c7.5,0,14.1-1.3,19.9-4c5.8-2.7,10.6-6.2,14.6-10.7c3.9-4.5,6.9-9.7,8.9-15.7c2-6,3-12.4,3-19.1c0-6.7-1-13-3-19.1
|
||||
c-2-6-5-11.3-8.9-15.8c-3.9-4.5-8.8-8-14.6-10.7C832.1,116.8,825.5,115.4,818,115.4L818,115.4z M818,125.6c5.7,0,10.7,1.1,15,3.4
|
||||
c4.3,2.3,7.9,5.2,10.8,8.9c2.8,3.7,5,7.9,6.5,12.6c1.5,4.7,2.2,9.5,2.2,14.4c0,4.9-0.7,9.7-2.2,14.4c-1.5,4.7-3.6,8.9-6.5,12.6
|
||||
c-2.9,3.7-6.4,6.7-10.8,9c-4.3,2.3-9.3,3.4-15,3.4c-5.7,0-10.7-1.1-15-3.4c-4.3-2.3-7.9-5.3-10.8-9c-2.8-3.7-5-7.9-6.5-12.6
|
||||
c-1.5-4.7-2.2-9.5-2.2-14.4c0-4.9,0.7-9.7,2.2-14.4c1.5-4.7,3.6-8.9,6.5-12.6c2.9-3.7,6.4-6.7,10.8-8.9
|
||||
C807.3,126.7,812.3,125.6,818,125.6L818,125.6z"/>
|
||||
<g>
|
||||
<path class="st0" d="M881.2,122.1c0,5.4-4.1,9.6-9.4,9.6c-5.3,0-9.4-4.2-9.4-9.6c0-5.4,4.1-9.6,9.4-9.6
|
||||
C877.1,112.5,881.2,116.7,881.2,122.1z M863.9,122.1c0,4.6,3.4,8.2,7.9,8.2c4.5,0,7.9-3.6,7.9-8.2c0-4.6-3.4-8.2-7.9-8.2
|
||||
C867.3,113.9,863.9,117.5,863.9,122.1z M869.8,127.6h-1.3v-11.1h4.2c2.4,0,3.5,1.1,3.5,3.1c0,2-1.3,2.8-2.7,3.1l3.2,4.9h-1.6
|
||||
l-3.1-4.9h-2.2V127.6z M871.5,121.5c1.6,0,3.3-0.1,3.3-1.9c0-1.5-1.3-1.9-2.6-1.9h-2.4v3.8H871.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M139.5,142.6l-32.2-32.8l-1.2-1.2c-18.4-18.9-48.5-18.9-66.8-0.1c-18.5,18.8-18.5,49.3,0,68.1
|
||||
c18.3,18.8,48.3,18.8,66.7,0L139.5,142.6z"/>
|
||||
</g>
|
||||
<path class="st1" d="M139.5,142.6V189l0.1,1.7c0,26.6-21.2,48.3-47.3,48.3c-26.1,0-47.3-21.5-47.3-48.1
|
||||
c0-26.6,21.2-48.2,47.3-48.2H139.5z"/>
|
||||
<path class="st1" d="M139.5,142.6V189l0.1,1.7c0,26.6-21.2,48.3-47.3,48.3c-26.1,0-47.3-21.5-47.3-48.1
|
||||
c0-26.6,21.2-48.2,47.3-48.2H139.5z"/>
|
||||
<path class="st1" d="M139.5,142.6V189l0.1,1.7c0,26.6-21.2,48.3-47.3,48.3c-26.1,0-47.3-21.5-47.3-48.1c0-3,0.3-5.8,0.8-8.6
|
||||
c18.4,13.1,43.9,11.2,60.3-5.6L139.5,142.6L139.5,142.6z"/>
|
||||
<path class="st2" d="M92.3,148.7c-22.7,0-41.2,18.9-41.2,42.1c0,23.2,18.5,42,41.2,42c22.8,0,41.2-18.8,41.2-42V189v-40.3H92.3
|
||||
L92.3,148.7z"/>
|
||||
<path class="st1" d="M139.5,142.6l32.1,32.8l1.3,1.2c18.4,18.8,18.4,49.4,0,68.2c-18.5,18.8-48.4,18.8-66.9,0
|
||||
c-18.4-18.8-18.4-49.3,0-68.1L139.5,142.6z"/>
|
||||
<path class="st2" d="M110.4,180.9c-16.1,16.4-16.1,43.1,0,59.5c16.1,16.4,42.2,16.4,58.3,0c16.1-16.4,16.1-43.1,0-59.5l-1.3-1.2
|
||||
l-27.9-28.5L110.4,180.9L110.4,180.9z"/>
|
||||
<path class="st1" d="M139.5,142.6H185l1.7-0.1c26.1,0,47.3,21.6,47.3,48.2c0,26.6-21.1,48.1-47.2,48.1
|
||||
c-26.1,0-47.3-21.5-47.3-48.1V142.6z"/>
|
||||
<path class="st2" d="M145.5,190.7c0,23.2,18.4,42.1,41.2,42.1c22.7,0,41.1-18.8,41.1-42.1c0-23.3-18.4-42-41.1-42H185h-39.5
|
||||
v41.9V190.7z"/>
|
||||
<path class="st1" d="M139.5,142.6l32.1-32.8l1.2-1.2c18.4-18.8,48.4-18.9,66.8-0.1c18.4,18.8,18.4,49.3,0,68.1
|
||||
c-18.4,18.8-48.4,18.8-66.8,0L139.5,142.6z"/>
|
||||
<path class="st2" d="M177.1,172.2c16,16.5,42.1,16.5,58.2,0c16.2-16.4,16.2-43,0-59.4c-16.1-16.4-42.2-16.4-58.2,0l-1.3,1.3
|
||||
L148,142.6l29,29.6L177.1,172.2z"/>
|
||||
<path class="st1" d="M139.5,142.6V96.2v-1.7c0-26.6,21.1-48.2,47.3-48.2c26,0,47.1,21.5,47.1,48.1c0,26.6-21.1,48.2-47.1,48.2
|
||||
H139.5z"/>
|
||||
<g>
|
||||
<path class="st3" d="M186.7,136.5c22.7,0,41.1-18.8,41.1-42.1c0-23.2-18.4-42-41.1-42c-22.8,0-41.3,18.9-41.3,42l0.1,1.8v40.3
|
||||
h41H186.7z"/>
|
||||
<path class="st1" d="M139.5,142.6l-32.2-32.8l-1.3-1.2c-18.4-18.9-18.4-49.4,0-68.2c18.3-18.8,48.3-18.8,66.8,0
|
||||
c18.4,18.8,18.4,49.3,0,68.1L139.5,142.6z"/>
|
||||
</g>
|
||||
<path class="st4" d="M168.6,104.6c16.1-16.4,16.1-43.1,0-59.5c-16.1-16.4-42.2-16.4-58.3,0c-16.1,16.4-16.1,43.1,0,59.5l1.2,1.3
|
||||
l28,28.4L168.6,104.6L168.6,104.6z"/>
|
||||
<path class="st1" d="M139.5,143H94l-1.7,0c-26,0-47.3-21.6-47.3-48.2c0-26.6,21.2-48.2,47.2-48.2c26.1,0,47.3,21.6,47.3,48.2
|
||||
V143z"/>
|
||||
<path class="st4" d="M133.5,94.9c0-23.2-18.4-42-41.3-42c-22.7,0-41.2,18.8-41.2,42c0,23.2,18.5,42.1,41.2,42.1l1.8,0h39.5V94.9
|
||||
L133.5,94.9z"/>
|
||||
<path class="st1" d="M139.5,143l-114,0c0-12.3,4.5-24.7,13.9-34.1c18.3-18.8,48.3-18.8,66.7,0L139.5,143z"/>
|
||||
<path class="st4" d="M101.8,113.3L131,143H92.3c-20.9,0-38.7,13.9-44.9,33.2c-1.3-1.1-2.6-2.2-3.8-3.5
|
||||
c-16.2-16.4-16.2-43,0-59.5C59.6,96.9,85.8,96.9,101.8,113.3"/>
|
||||
<path class="st5" d="M101.9,113.3l23.1,23.5l-32.8,0.1c-18.9,0-34.8-12.9-39.7-30.6C68.2,97.4,88.7,99.5,101.9,113.3
|
||||
L101.9,113.3z"/>
|
||||
<path class="st5" d="M133.5,94.9l0,33.3l-23.2-23.6C97,91,94.6,70.4,103.4,54.4C120.7,59.3,133.5,75.6,133.5,94.9L133.5,94.9z"
|
||||
/>
|
||||
<path class="st5" d="M145.3,128.3V94.9c0-19.3,12.8-35.5,30.1-40.5c8.8,16,7.1,36.1-6.6,50L145.3,128.3z"/>
|
||||
<path class="st6" d="M153.9,136.5l23.1-23.7c13.3-13.6,33.6-15.9,49.3-7c-5,17.7-20.1,30.3-38.7,30.7L153.9,136.5z"/>
|
||||
<path class="st7" d="M153.9,148.7h32.8c18.9,0,34.8,12.9,39.7,30.6c-15.8,9-36,6.6-49.4-7L153.9,148.7z"/>
|
||||
<path class="st7" d="M145.5,190.5v-33.2l23.2,23.6c13.3,13.7,15.6,34.4,6.8,50.4c-17.3-5-30-21.3-30-40.5V190.5z"/>
|
||||
<path class="st7" d="M110.4,180.8l23.1-23.5l0.1,33.4c0,19.3-12.7,35.5-30,40.4C94.7,215.2,97.1,194.5,110.4,180.8L110.4,180.8z
|
||||
"/>
|
||||
<path class="st7" d="M92.5,148.7h32.6L102,172.3c-13.3,13.6-33.7,16-49.4,7c4.9-17.7,20.9-30.6,39.8-30.6H92.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<line class="st8" x1="864.6" y1="235" x2="272.8" y2="235"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M27.9,27.8l-5.2-5.2v7.8c0,4.3,3.5,7.8,7.8,7.8c4.3,0,7.8-3.5,7.8-7.8c0-0.3,0-0.6-0.1-1
|
||||
C34.9,31.1,30.7,30.6,27.9,27.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M39.8,16c-0.2-0.2-0.5-0.4-0.7-0.6c-1.2,3.6-4.5,6.1-8.5,6.1h-7.3l5.5,5.5l0,0c3.1,3,8,3,11.1,0
|
||||
C42.8,23.9,42.8,19,39.8,16z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st3" d="M30.5,4.8c-0.3,0-0.7,0-1,0.1c1.7,3.3,1.2,7.5-1.7,10.3l-5.2,5.2h7.8c4.3,0,7.8-3.5,7.8-7.8
|
||||
C38.4,8.3,34.9,4.8,30.5,4.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st4" d="M27.1,3.4c-3.1-3-8-3-11.1,0c-0.2,0.2-0.4,0.5-0.6,0.7c3.6,1.2,6.2,4.5,6.2,8.5v7.3l5.5-5.5l0,0
|
||||
C30.2,11.4,30.2,6.4,27.1,3.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st4" d="M15.3,15.2l0.2,0.2l5,4.9h0l0-7.8v0c0-4.3-3.5-7.8-7.8-7.8c-4.3,0-7.8,3.5-7.8,7.8c0,0.3,0,0.6,0.1,1
|
||||
C8.3,11.9,12.5,12.4,15.3,15.2z"/>
|
||||
<path class="st1" d="M21.6,21.5L0,21.5c0-2.3,0.9-4.6,2.6-6.3c3.5-3.5,9.2-3.5,12.7,0L21.6,21.5L21.6,21.5z"/>
|
||||
<path class="st1" d="M21.6,21.5v8.6l0,0.3c0,4.9-4,8.9-9,8.9c-4.9,0-9-4-9-8.9c0-4.9,4-8.9,9-8.9H21.6L21.6,21.5z"/>
|
||||
<path class="st4" d="M4.2,27.6c0.1-0.2,0.1-0.4,0.2-0.5c0,0,0,0,0-0.1c0.1-0.2,0.2-0.4,0.3-0.6c0,0,0-0.1,0.1-0.1
|
||||
c0.1-0.2,0.2-0.4,0.3-0.5c0,0,0,0,0-0.1c0.1-0.2,0.2-0.3,0.3-0.5c0,0,0-0.1,0.1-0.1c0.1-0.2,0.2-0.3,0.4-0.5c0,0,0.1-0.1,0.1-0.1
|
||||
c0.1-0.1,0.3-0.3,0.4-0.4c0,0,0,0,0,0c0.1-0.1,0.3-0.3,0.4-0.4c0,0,0.1-0.1,0.1-0.1c0.2-0.1,0.3-0.3,0.5-0.4c0,0,0,0,0.1,0
|
||||
c0.2-0.1,0.3-0.2,0.5-0.3c0,0,0.1,0,0.1-0.1c0.2-0.1,0.3-0.2,0.5-0.3c0,0,0.1,0,0.1-0.1c0.2-0.1,0.4-0.2,0.5-0.3c0,0,0,0,0,0
|
||||
C9.4,22.1,9.6,22,9.8,22c0,0,0.1,0,0.1,0c0.2-0.1,0.4-0.1,0.6-0.2c0,0,0.1,0,0.1,0c0.2,0,0.4-0.1,0.6-0.1c0,0,0.1,0,0.1,0
|
||||
c0.2,0,0.4-0.1,0.6-0.1c0,0,0.1,0,0.1,0c0.2,0,0.4,0,0.7,0H20L14.5,16c-3.1-3-8-3-11.1,0c-3.1,3-3.1,8,0,11
|
||||
C3.7,27.2,3.9,27.4,4.2,27.6C4.2,27.6,4.2,27.6,4.2,27.6z"/>
|
||||
<path class="st5" d="M14.5,16l4.4,4.3H13l-0.3,0c-3.6,0-6.6-2.4-7.5-5.7C8.1,13,12,13.5,14.5,16L14.5,16L14.5,16z"/>
|
||||
<path class="st5" d="M20.5,12.6v6.2l-4.2-4.1l-0.2-0.2c-2.5-2.5-3-6.3-1.3-9.3C18.1,6,20.5,9,20.5,12.6L20.5,12.6L20.5,12.6z"/>
|
||||
<path class="st5" d="M27.1,14.4l-4.4,4.3v-5.8l0-0.3c0-3.6,2.4-6.6,5.7-7.5C30.1,8,29.7,11.9,27.1,14.4L27.1,14.4L27.1,14.4z"/>
|
||||
<path class="st6" d="M30.5,20.3h-6.2l4.2-4.1l0.2-0.2c2.5-2.5,6.4-2.9,9.3-1.3C37.2,17.9,34.1,20.3,30.5,20.3L30.5,20.3L30.5,20.3z
|
||||
"/>
|
||||
<path class="st7" d="M28.7,27l-4.4-4.3h5.9l0.3,0c3.6,0,6.6,2.4,7.5,5.7C35.1,29.9,31.3,29.5,28.7,27L28.7,27L28.7,27z"/>
|
||||
<path class="st7" d="M22.8,30.4v-6.2l4.2,4.1l0.2,0.2c2.5,2.5,3,6.3,1.3,9.3C25.2,36.9,22.8,33.9,22.8,30.4L22.8,30.4L22.8,30.4z"
|
||||
/>
|
||||
<path class="st7" d="M16.1,28.6l4.4-4.3v5.8l0,0.3c0,3.6-2.4,6.6-5.7,7.5C13.1,34.9,13.6,31.1,16.1,28.6L16.1,28.6L16.1,28.6z"/>
|
||||
<path class="st7" d="M12.7,22.6h6.2l-4.2,4.1L14.5,27C12,29.5,8.1,30,5.1,28.3C6.1,25,9.1,22.6,12.7,22.6L12.7,22.6L12.7,22.6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,20 @@
|
||||
<div id="adf-buttons-menu" class="adf-buttons-menu" *ngIf="!isMenuEmpty">
|
||||
<div *ngIf="isMobile()">
|
||||
<button mat-icon-button [matMenuTriggerFor]="editReportMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #editReportMenu="matMenu" class="adf-buttons-menu-mobile">
|
||||
<ng-content *ngTemplateOutlet="desktop">
|
||||
</ng-content>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!isMobile()" class="adf-buttons-menu-desktop">
|
||||
<ng-content *ngTemplateOutlet="desktop">
|
||||
</ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #desktop>
|
||||
<ng-content></ng-content>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,32 @@
|
||||
@mixin adf-buttons-menu-theme($theme) {
|
||||
|
||||
.adf-buttons-menu {
|
||||
margin-right: 10px;
|
||||
|
||||
& div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&-mobile {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&-desktop {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
color: black;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button > mat-icon.mat-icon.material-icons {
|
||||
color: black;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { TestBed, async } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-custom-container',
|
||||
template: `
|
||||
<adf-buttons-action-menu>
|
||||
<button mat-menu-item (click)="assignValue()">
|
||||
<mat-icon>settings</mat-icon><span> Button </span>
|
||||
</button>
|
||||
</adf-buttons-action-menu>
|
||||
`
|
||||
})
|
||||
export class CustomContainerComponent {
|
||||
|
||||
value: number;
|
||||
|
||||
assignValue() {
|
||||
this.value = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'adf-custom-empty-container',
|
||||
template: `
|
||||
<adf-buttons-action-menu>
|
||||
</adf-buttons-action-menu>
|
||||
`
|
||||
})
|
||||
export class CustomEmptyContainerComponent {
|
||||
}
|
||||
|
||||
describe('ButtonsMenuComponent', () => {
|
||||
|
||||
describe('When Buttons are injected', () => {
|
||||
|
||||
let fixture;
|
||||
let component: CustomContainerComponent;
|
||||
let element: HTMLElement;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreTestingModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
CustomContainerComponent
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CustomContainerComponent);
|
||||
element = fixture.debugElement.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render buttons menu when at least one button is declared', async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const buttonsMenuElement = element.querySelector('#adf-buttons-menu');
|
||||
expect(buttonsMenuElement).toBeDefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should trigger event when a specific button is clicked', async(() => {
|
||||
expect(component.value).toBeUndefined();
|
||||
let button = element.querySelector('button');
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.value).toBe(1);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('When no buttons are injected', () => {
|
||||
let fixture;
|
||||
let element: HTMLElement;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreTestingModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
CustomEmptyContainerComponent
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CustomEmptyContainerComponent);
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should hide buttons menu if buttons input is empty', async(() => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const buttonsMenuElement = element.querySelector('#adf-buttons-menu');
|
||||
expect(buttonsMenuElement).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
|
||||
import { MatMenuItem } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-buttons-action-menu',
|
||||
templateUrl: './buttons-menu.component.html',
|
||||
styleUrls: ['./buttons-menu.component.scss']
|
||||
})
|
||||
|
||||
export class ButtonsMenuComponent implements AfterContentInit {
|
||||
|
||||
@ContentChildren(MatMenuItem) buttons: QueryList<MatMenuItem>;
|
||||
|
||||
isMenuEmpty: boolean;
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.buttons.length > 0) {
|
||||
this.isMenuEmpty = false;
|
||||
} else {
|
||||
this.isMenuEmpty = true;
|
||||
}
|
||||
}
|
||||
|
||||
isMobile(): boolean {
|
||||
return !!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
}
|
||||
}
|
||||
+11
-15
@@ -16,28 +16,24 @@
|
||||
*/
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MaterialModule } from '../material.module';
|
||||
|
||||
import { SidebarActionMenuComponent, SidebarMenuDirective, SidebarMenuExpandIconDirective, SidebarMenuTitleIconDirective } from './sidebar-action-menu.component';
|
||||
|
||||
export function declarations() {
|
||||
return [
|
||||
SidebarActionMenuComponent,
|
||||
SidebarMenuDirective,
|
||||
SidebarMenuExpandIconDirective,
|
||||
SidebarMenuTitleIconDirective
|
||||
];
|
||||
}
|
||||
import { ButtonsMenuComponent } from './buttons-menu.component';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialModule,
|
||||
TranslateModule,
|
||||
FlexLayoutModule
|
||||
],
|
||||
declarations: declarations(),
|
||||
exports: declarations()
|
||||
declarations: [
|
||||
ButtonsMenuComponent
|
||||
],
|
||||
exports: [
|
||||
ButtonsMenuComponent
|
||||
]
|
||||
})
|
||||
export class SideBarActionModule {}
|
||||
export class ButtonsMenuModule {}
|
||||
@@ -15,4 +15,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './sidebar-action-menu.component';
|
||||
export * from './public-api';
|
||||
@@ -0,0 +1,19 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './buttons-menu.component';
|
||||
export * from './buttons-menu.module';
|
||||
+17
-41
@@ -15,47 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { MatCheckboxChange, MatCheckbox } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { MatCheckbox, MatCheckboxChange } from '@angular/material';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewBoolItemComponent } from './card-view-boolitem.component';
|
||||
import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('CardViewBoolItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewBoolItemComponent>;
|
||||
let component: CardViewBoolItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MaterialModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewBoolItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardViewUpdateService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewBoolItemComponent);
|
||||
@@ -71,7 +47,6 @@ describe('CardViewBoolItemComponent', () => {
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -89,7 +64,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT render the label and value if the property is NOT editable and doesn\'t have a proper boolean value set' , () => {
|
||||
it('should NOT render the label and value if the property is NOT editable and doesn\'t have a proper boolean value set', () => {
|
||||
component.editable = true;
|
||||
component.property.value = undefined;
|
||||
component.property.editable = false;
|
||||
@@ -102,7 +77,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the label and value if the property is NOT editable but has a proper boolean value set' , () => {
|
||||
it('should render the label and value if the property is NOT editable but has a proper boolean value set', () => {
|
||||
component.editable = true;
|
||||
component.property.value = false;
|
||||
component.property.editable = false;
|
||||
@@ -115,7 +90,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should render ticked checkbox if property\'s value is true', () => {
|
||||
it('should render ticked checkbox if property value is true', () => {
|
||||
component.property.value = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -124,7 +99,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value.nativeElement.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should render ticked checkbox if property\'s value is not set but default is true and editable', () => {
|
||||
it('should render ticked checkbox if property value is not set but default is true and editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = undefined;
|
||||
@@ -136,7 +111,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value.nativeElement.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('should render unticked checkbox if property\'s value is false', () => {
|
||||
it('should render unticked checkbox if property value is false', () => {
|
||||
component.property.value = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -145,7 +120,7 @@ describe('CardViewBoolItemComponent', () => {
|
||||
expect(value.nativeElement.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('should render unticked checkbox if property\'s value is not set but default is false and editable', () => {
|
||||
it('should render unticked checkbox if property value is not set but default is false and editable', () => {
|
||||
component.editable = true;
|
||||
component.property.editable = true;
|
||||
component.property.value = undefined;
|
||||
@@ -204,15 +179,15 @@ describe('CardViewBoolItemComponent', () => {
|
||||
const cardViewUpdateService = TestBed.get(CardViewUpdateService);
|
||||
spyOn(cardViewUpdateService, 'update');
|
||||
|
||||
component.changed(<MatCheckboxChange> {checked: true});
|
||||
component.changed(<MatCheckboxChange> { checked: true });
|
||||
|
||||
expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, true);
|
||||
});
|
||||
|
||||
it('should update the propery\'s value after a changed', async(() => {
|
||||
it('should update the property value after a changed', async(() => {
|
||||
component.property.value = true;
|
||||
|
||||
component.changed(<MatCheckboxChange> {checked: false});
|
||||
component.changed(<MatCheckboxChange> { checked: false });
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.property.value).toBe(false);
|
||||
@@ -224,10 +199,11 @@ describe('CardViewBoolItemComponent', () => {
|
||||
component.property.value = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
let disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({ boolkey: true });
|
||||
disposableUpdate.unsubscribe();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
+13
-38
@@ -15,48 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatDatepickerModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import moment from 'moment-es6';
|
||||
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewDateItemComponent } from './card-view-dateitem.component';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('CardViewDateItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewDateItemComponent>;
|
||||
let component: CardViewDateItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
MatDatepickerModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
MatNativeDatetimeModule,
|
||||
MatDatetimepickerModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewDateItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardViewUpdateService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewDateItemComponent);
|
||||
@@ -73,7 +48,6 @@ describe('CardViewDateItemComponent', () => {
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render the label and value', () => {
|
||||
@@ -89,7 +63,7 @@ describe('CardViewDateItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, editable:false and displayEmpty is false', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
component.property = new CardViewDateItemModel({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
@@ -107,7 +81,7 @@ describe('CardViewDateItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, editable:false and displayEmpty is true', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
component.property = new CardViewDateItemModel({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
@@ -125,7 +99,7 @@ describe('CardViewDateItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and editable:true', () => {
|
||||
component.property = new CardViewDateItemModel ({
|
||||
component.property = new CardViewDateItemModel({
|
||||
label: 'Date label',
|
||||
value: '',
|
||||
key: 'datekey',
|
||||
@@ -202,15 +176,16 @@ describe('CardViewDateItemComponent', () => {
|
||||
const expectedDate = moment('Jul 10 2017', 'MMM DD YY');
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
let disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({datekey: expectedDate.toDate()});
|
||||
expect(updateNotification.changed).toEqual({ datekey: expectedDate.toDate() });
|
||||
disposableUpdate.unsubscribe();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
component.onDateChanged({value: expectedDate});
|
||||
component.onDateChanged({ value: expectedDate });
|
||||
});
|
||||
|
||||
it('should update the propery\'s value after a succesful update attempt', async(() => {
|
||||
@@ -220,7 +195,7 @@ describe('CardViewDateItemComponent', () => {
|
||||
const expectedDate = moment('Jul 10 2017', 'MMM DD YY');
|
||||
fixture.detectChanges();
|
||||
|
||||
component.onDateChanged({value: expectedDate});
|
||||
component.onDateChanged({ value: expectedDate });
|
||||
|
||||
fixture.whenStable().then(
|
||||
(updateNotification) => {
|
||||
|
||||
+8
-32
@@ -15,19 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { CardViewMapItemModel } from '../../models/card-view-mapitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewMapItemComponent } from './card-view-mapitem.component';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('CardViewMapItemComponent', () => {
|
||||
let service: CardViewUpdateService;
|
||||
@@ -37,28 +32,9 @@ describe('CardViewMapItemComponent', () => {
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MaterialModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewMapItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardViewUpdateService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewMapItemComponent);
|
||||
@@ -70,7 +46,6 @@ describe('CardViewMapItemComponent', () => {
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render the default if the value is empty and displayEmpty is true', () => {
|
||||
@@ -143,11 +118,12 @@ describe('CardViewMapItemComponent', () => {
|
||||
fixture.detectChanges();
|
||||
let value: any = element.querySelector('.adf-mapitem-clickable-value');
|
||||
|
||||
service.itemClicked$.subscribe((response) => {
|
||||
let disposableUpdate = service.itemClicked$.subscribe((response) => {
|
||||
expect(response.target).not.toBeNull();
|
||||
expect(response.target.type).toEqual('map');
|
||||
expect(response.target.clickable).toBeTruthy();
|
||||
expect(response.target.displayValue).toEqual('fakeProcessName');
|
||||
disposableUpdate.unsubscribe();
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
+17
-44
@@ -15,54 +15,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
|
||||
import { CardViewTextItemComponent } from './card-view-textitem.component';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('CardViewTextItemComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewTextItemComponent>;
|
||||
let component: CardViewTextItemComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
NoopAnimationsModule,
|
||||
MatDatepickerModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewTextItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardViewUpdateService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewTextItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: 'Lorem ipsum',
|
||||
key: 'textkey',
|
||||
@@ -73,7 +46,6 @@ describe('CardViewTextItemComponent', () => {
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -91,7 +63,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, editable is false and displayEmpty is false', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -107,7 +79,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, editable is false and displayEmpty is true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -123,7 +95,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and editable true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -139,7 +111,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should NOT render the default as value if the value is empty, clickable is false and displayEmpty is false', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -155,7 +127,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty, clickable is false and displayEmpty is true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -171,7 +143,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the default as value if the value is empty and clickable true', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -186,7 +158,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should render the edit icon in case of clickable true and icon defined', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -202,7 +174,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should not render the edit icon in case of clickable true and icon undefined', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -216,7 +188,7 @@ describe('CardViewTextItemComponent', () => {
|
||||
});
|
||||
|
||||
it('should not render the edit icon in case of clickable false and icon defined', () => {
|
||||
component.property = new CardViewTextItemModel ({
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: '',
|
||||
key: 'textkey',
|
||||
@@ -342,10 +314,11 @@ describe('CardViewTextItemComponent', () => {
|
||||
const expectedText = 'changed text';
|
||||
fixture.detectChanges();
|
||||
|
||||
cardViewUpdateService.itemUpdated$.subscribe(
|
||||
let disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe(
|
||||
(updateNotification) => {
|
||||
expect(updateNotification.target).toBe(component.property);
|
||||
expect(updateNotification.changed).toEqual({ textkey: expectedText });
|
||||
disposableUpdate.unsubscribe();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -41,7 +41,8 @@ export class CardViewTextItemComponent implements OnChanges {
|
||||
editedValue: string;
|
||||
errorMessages: string[];
|
||||
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService) {}
|
||||
constructor(private cardViewUpdateService: CardViewUpdateService) {
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.editedValue = this.property.value;
|
||||
@@ -83,7 +84,7 @@ export class CardViewTextItemComponent implements OnChanges {
|
||||
|
||||
update(): void {
|
||||
if (this.property.isValid(this.editedValue)) {
|
||||
this.cardViewUpdateService.update(this.property, this.editedValue );
|
||||
this.cardViewUpdateService.update(this.property, this.editedValue);
|
||||
this.property.value = this.editedValue;
|
||||
this.setEditMode(false);
|
||||
} else {
|
||||
|
||||
@@ -16,78 +16,31 @@
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatDatepickerModule, MatIconModule, MatInputModule, MatNativeDateModule } from '@angular/material';
|
||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CardViewDateItemModel } from '../../models/card-view-dateitem.model';
|
||||
import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
|
||||
import { TranslateLoaderService } from '../../../services/translate-loader.service';
|
||||
import { CardViewContentProxyDirective } from '../../directives/card-view-content-proxy.directive';
|
||||
import { CardViewDateItemComponent } from '../card-view-dateitem/card-view-dateitem.component';
|
||||
import { CardItemTypeService } from '../../services/card-item-types.service';
|
||||
import { CardViewItemDispatcherComponent } from '../card-view-item-dispatcher/card-view-item-dispatcher.component';
|
||||
import { CardViewTextItemComponent } from '../card-view-textitem/card-view-textitem.component';
|
||||
import { CardViewComponent } from './card-view.component';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('CardViewComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<CardViewComponent>;
|
||||
let component: CardViewComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
MatDatepickerModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatNativeDateModule,
|
||||
MatDatetimepickerModule,
|
||||
MatNativeDatetimeModule,
|
||||
FormsModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderService
|
||||
}
|
||||
})
|
||||
],
|
||||
declarations: [
|
||||
CardViewComponent,
|
||||
CardViewItemDispatcherComponent,
|
||||
CardViewContentProxyDirective,
|
||||
CardViewTextItemComponent,
|
||||
CardViewDateItemComponent
|
||||
],
|
||||
providers: [
|
||||
CardItemTypeService,
|
||||
CardViewUpdateService
|
||||
]
|
||||
});
|
||||
|
||||
// entryComponents are not supported yet on TestBed, that is why this ugly workaround:
|
||||
// https://github.com/angular/angular/issues/10760
|
||||
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||
set: { entryComponents: [ CardViewTextItemComponent, CardViewDateItemComponent ] }
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should render the label and value', async(() => {
|
||||
component.properties = [new CardViewTextItemModel({label: 'My label', value: 'My value', key: 'some key'})];
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -24,12 +24,15 @@ import { CardViewItem } from '../../interfaces/card-view-item.interface';
|
||||
styleUrls: ['./card-view.component.scss']
|
||||
})
|
||||
export class CardViewComponent {
|
||||
/** (**required**) Items to show in the card view. */
|
||||
@Input()
|
||||
properties: CardViewItem [];
|
||||
|
||||
/** Toggles whether or not the items can be edited. */
|
||||
@Input()
|
||||
editable: boolean;
|
||||
|
||||
/** Toggles whether or not to show empty items in non-editable mode. */
|
||||
@Input()
|
||||
displayEmpty: boolean = true;
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ export interface CardViewItemProperties {
|
||||
clickable?: boolean;
|
||||
icon?: string;
|
||||
validators?: CardViewItemValidator[];
|
||||
data?: any;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import { CardViewItemValidator } from '../interfaces/card-view.interfaces';
|
||||
class CarViewCustomItemModel extends CardViewBaseItemModel {}
|
||||
|
||||
describe('CardViewBaseItemModel', () => {
|
||||
|
||||
let properties: CardViewItemProperties;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -33,8 +32,22 @@ describe('CardViewBaseItemModel', () => {
|
||||
};
|
||||
});
|
||||
|
||||
describe('isValid & Validation errors', () => {
|
||||
describe('CardViewItemProperties', () => {
|
||||
it('data should be null if no data is set', () => {
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
expect(itemModel.data).toBe(null);
|
||||
});
|
||||
|
||||
it('data should be stored if one data is set', () => {
|
||||
properties.data = { name: 'ryuk', lifeform: 'shinigami' };
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
expect(itemModel.data).toBe(properties.data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValid & Validation errors', () => {
|
||||
it('should be true when no validators are set', () => {
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
@@ -48,7 +61,7 @@ describe('CardViewBaseItemModel', () => {
|
||||
const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' };
|
||||
spyOn(validator1, 'isValid');
|
||||
spyOn(validator2, 'isValid');
|
||||
properties.validators = [ validator1, validator2 ];
|
||||
properties.validators = [validator1, validator2];
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
itemModel.isValid('test-against-this');
|
||||
@@ -57,10 +70,10 @@ describe('CardViewBaseItemModel', () => {
|
||||
expect(validator2.isValid).toHaveBeenCalledWith('test-against-this');
|
||||
});
|
||||
|
||||
it('should return the registered validators\' common decision (case true)', () => {
|
||||
it("should return the registered validators' common decision (case true)", () => {
|
||||
const validator1: CardViewItemValidator = { isValid: () => true, message: 'validator 1' };
|
||||
const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' };
|
||||
properties.validators = [ validator1, validator2 ];
|
||||
properties.validators = [validator1, validator2];
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
const isValid = itemModel.isValid('test-against-this');
|
||||
@@ -69,11 +82,11 @@ describe('CardViewBaseItemModel', () => {
|
||||
expect(itemModel.getValidationErrors('test-against-this')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return the registered validators\' common decision (case false)', () => {
|
||||
it("should return the registered validators' common decision (case false)", () => {
|
||||
const validator1: CardViewItemValidator = { isValid: () => false, message: 'validator 1' };
|
||||
const validator2: CardViewItemValidator = { isValid: () => true, message: 'validator 2' };
|
||||
const validator3: CardViewItemValidator = { isValid: () => false, message: 'validator 3' };
|
||||
properties.validators = [ validator1, validator2, validator3 ];
|
||||
properties.validators = [validator1, validator2, validator3];
|
||||
const itemModel = new CarViewCustomItemModel(properties);
|
||||
|
||||
const isValid = itemModel.isValid('test-against-this');
|
||||
|
||||
@@ -26,6 +26,7 @@ export abstract class CardViewBaseItemModel {
|
||||
clickable: boolean;
|
||||
icon?: string;
|
||||
validators?: CardViewItemValidator[];
|
||||
data?: any;
|
||||
|
||||
constructor(obj: CardViewItemProperties) {
|
||||
this.label = obj.label || '';
|
||||
@@ -36,6 +37,7 @@ export abstract class CardViewBaseItemModel {
|
||||
this.clickable = !!obj.clickable;
|
||||
this.icon = obj.icon || '';
|
||||
this.validators = obj.validators || [];
|
||||
this.data = obj.data || null;
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
@@ -48,7 +50,7 @@ export abstract class CardViewBaseItemModel {
|
||||
}
|
||||
|
||||
return this.validators
|
||||
.map((validator) => validator.isValid(newValue))
|
||||
.map(validator => validator.isValid(newValue))
|
||||
.reduce((isValidUntilNow, isValid) => isValidUntilNow && isValid, true);
|
||||
}
|
||||
|
||||
@@ -57,8 +59,6 @@ export abstract class CardViewBaseItemModel {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.validators
|
||||
.filter((validator) => !validator.isValid(value))
|
||||
.map((validator) => validator.message);
|
||||
return this.validators.filter(validator => !validator.isValid(value)).map(validator => validator.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
<div class="adf-panel adf-panel-default" [ngClass]="{'adf-panel-open': isOpen}">
|
||||
<div class="adf-panel-heading" [ngClass]="{'adf-panel-heading-selected': isSelected}" (click)="toggleOpen($event)">
|
||||
<div id="heading-icon" *ngIf="hasHeadingIcon()" class="adf-panel-heading-icon">
|
||||
<mat-icon class="material-icons"
|
||||
[matTooltip]="headingIconTooltip"
|
||||
[matTooltipDisabled]="!headingIconTooltip">
|
||||
{{headingIcon}}
|
||||
</mat-icon>
|
||||
</div>
|
||||
<div id="heading-text" class="adf-panel-heading-text">{{heading}}</div>
|
||||
<div id="accordion-button" *ngIf="hasAccordionIcon" class="adf-panel-heading-toggle">
|
||||
<i class="material-icons">{{getAccordionIcon()}}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adf-panel-collapse" [hidden]="!isOpen">
|
||||
<div class="adf-panel-body" #contentWrapper>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-accordion class="adf-panel">
|
||||
<mat-expansion-panel #expansionPanel
|
||||
id="adf-expansion-panel-id"
|
||||
(click)="isExpandable($event)"
|
||||
[expanded]="toggleExpansion()"
|
||||
(opened)="onHeaderClick()"
|
||||
[hideToggle]="!hasAccordionIcon">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<div class="adf-panel-heading" [ngClass]="{'adf-panel-heading-selected': isSelected}">
|
||||
<div id="heading-icon" *ngIf="hasHeadingIcon()" class="adf-panel-heading-icon">
|
||||
<mat-icon class="material-icons"
|
||||
[matTooltip]="headingIconTooltip"
|
||||
[matTooltipDisabled]="!headingIconTooltip">
|
||||
{{headingIcon}}
|
||||
</mat-icon>
|
||||
</div>
|
||||
<div id="heading-text" class="adf-panel-heading-text">{{heading}}</div>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div id="adf-expansion-panel-content-id" #contentWrapper>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
@@ -1,43 +1,55 @@
|
||||
@mixin adf-accordion-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
.adf-panel-heading {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.adf-panel {
|
||||
|
||||
.adf-panel-heading-selected {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
&-heading {
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-stretch: normal;
|
||||
line-height: normal;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.adf-panel-heading-icon {
|
||||
float: left;
|
||||
}
|
||||
&-heading-selected {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
.adf-panel-body {
|
||||
display: inline-block;
|
||||
}
|
||||
&-heading-icon {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.adf-panel-heading-text {
|
||||
float: left;
|
||||
padding-left: 20px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
&-heading-text {
|
||||
float: left;
|
||||
padding-left: 20px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.adf-panel-heading-toggle {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mat-expansion-panel {
|
||||
transition: none !important;
|
||||
box-shadow: none !important;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.adf-panel-heading-toggle:hover {
|
||||
opacity: 0.4;
|
||||
.mat-expansion-panel-body {
|
||||
padding: 1px !important;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
margin-right: 0px !important;
|
||||
}
|
||||
|
||||
.mat-expansion-indicator {
|
||||
margin-right: 25px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('AccordionGroupComponent', () => {
|
||||
|
||||
@@ -26,82 +26,83 @@ describe('AccordionGroupComponent', () => {
|
||||
let component: AccordionGroupComponent;
|
||||
let element: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
AccordionGroupComponent
|
||||
],
|
||||
providers: [AccordionComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AccordionGroupComponent);
|
||||
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
});
|
||||
|
||||
it('should be closed by default', async(() => {
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerText = element.querySelector('#heading-text');
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
let headerIcon = element.querySelector('#heading-icon .material-icons');
|
||||
expect(headerIcon.innerText).toEqual('fake-icon');
|
||||
let headerToggle = element.querySelector('#accordion-button .material-icons');
|
||||
expect(headerToggle.innerText).toEqual('expand_more');
|
||||
});
|
||||
}));
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should be open when click', async(() => {
|
||||
it('should define mat-accordion ', async(() => {
|
||||
component.isSelected = true;
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.detectChanges();
|
||||
element.querySelector('#accordion-button').click();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let accordin = element.querySelector('mat-accordion');
|
||||
let expansionPanel = element.querySelector('mat-expansion-panel');
|
||||
let accordinHeader = element.querySelector('mat-expansion-panel-header');
|
||||
let content = element.querySelector('#adf-expansion-panel-content-id').innerHTML;
|
||||
expect(accordin).toBeDefined();
|
||||
expect(expansionPanel).toBeDefined();
|
||||
expect(accordinHeader).toBeDefined();
|
||||
expect(content).toEqual('<a>Test</a>');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be display accordion title and icon', async(() => {
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerText = element.querySelector('#heading-text');
|
||||
let headerIcon = element.querySelector('#adf-expansion-panel-id .material-icons');
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
let headerIcon = element.querySelector('#heading-icon .material-icons');
|
||||
expect(headerIcon.innerText).toEqual('fake-icon');
|
||||
let headerToggle = element.querySelector('#accordion-button .material-icons');
|
||||
expect(headerToggle.innerText).toEqual('expand_less');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show expand icon by default', async(() => {
|
||||
it('should be display only accordion title', async(() => {
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.headingIcon = '';
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerIcon = element.querySelector('#accordion-button');
|
||||
expect(headerIcon).toBeDefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should hide expand icon', async(() => {
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.hasAccordionIcon = false;
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerIcon = element.querySelector('#accordion-button');
|
||||
let headerText = element.querySelector('#heading-text');
|
||||
let headerIcon = element.querySelector('#adf-expansion-panel-id .material-icons');
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
expect(headerIcon).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be display accordion title and content', async(() => {
|
||||
component.isSelected = true;
|
||||
component.heading = 'Fake Header';
|
||||
component.headingIcon = 'fake-icon';
|
||||
component.contentWrapper.nativeElement.innerHTML = '<a>Test</a>';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let headerText = element.querySelector('#heading-text');
|
||||
let headerIcon = element.querySelector('#heading-icon .material-icons');
|
||||
let content = element.querySelector('#adf-expansion-panel-content-id').innerHTML;
|
||||
expect(headerText.innerText).toEqual('Fake Header');
|
||||
expect(headerIcon.innerText).toEqual('fake-icon');
|
||||
expect(content).toEqual('<a>Test</a>');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit an event when a heading clicked', async(() => {
|
||||
component.heading = 'Fake Header';
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
import { AfterViewInit , Component, ElementRef, EventEmitter, Input, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { MatExpansionPanel } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-accordion-group',
|
||||
@@ -24,12 +24,14 @@ import { AccordionComponent } from './accordion.component';
|
||||
styleUrls: ['./accordion-group.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AccordionGroupComponent implements OnDestroy {
|
||||
export class AccordionGroupComponent implements AfterViewInit {
|
||||
private _isOpen: boolean = false;
|
||||
private _isSelected: boolean = false;
|
||||
|
||||
@ViewChild('contentWrapper')
|
||||
contentWrapper: any;
|
||||
contentWrapper: ElementRef;
|
||||
|
||||
@ViewChild('expansionPanel') expansionPanel: MatExpansionPanel;
|
||||
|
||||
/** Title heading for the group. */
|
||||
@Input()
|
||||
@@ -43,7 +45,7 @@ export class AccordionGroupComponent implements OnDestroy {
|
||||
@Input()
|
||||
headingIconTooltip: string;
|
||||
|
||||
/** Should the (expanded) accordion icon be shown? */
|
||||
/** Should the (expanded) accordion icon be shown? */
|
||||
@Input()
|
||||
hasAccordionIcon: boolean = true;
|
||||
|
||||
@@ -55,9 +57,6 @@ export class AccordionGroupComponent implements OnDestroy {
|
||||
@Input()
|
||||
set isOpen(value: boolean) {
|
||||
this._isOpen = value;
|
||||
if (value) {
|
||||
this.accordion.closeOthers(this);
|
||||
}
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
@@ -74,26 +73,33 @@ export class AccordionGroupComponent implements OnDestroy {
|
||||
return this._isSelected;
|
||||
}
|
||||
|
||||
constructor(private accordion: AccordionComponent) {
|
||||
this.accordion.addGroup(this);
|
||||
}
|
||||
hasContent: boolean;
|
||||
|
||||
ngOnDestroy() {
|
||||
this.accordion.removeGroup(this);
|
||||
constructor() { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.hasContent = this.contentWrapper.nativeElement && this.contentWrapper.nativeElement.children.length > 0;
|
||||
}
|
||||
|
||||
hasHeadingIcon() {
|
||||
return this.headingIcon ? true : false;
|
||||
}
|
||||
|
||||
toggleOpen(event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
this.isOpen = !this.isOpen;
|
||||
onHeaderClick(): void {
|
||||
this.headingClick.emit(this.heading);
|
||||
}
|
||||
|
||||
getAccordionIcon(): string {
|
||||
return this.isOpen ? 'expand_less' : 'expand_more';
|
||||
isExpandable(event: any) {
|
||||
if (!this.hasContent || !this.isOpen) {
|
||||
this.expandPanel();
|
||||
}
|
||||
}
|
||||
|
||||
expandPanel() {
|
||||
this.expansionPanel.expanded = !this.expansionPanel.expanded;
|
||||
}
|
||||
|
||||
toggleExpansion(): boolean {
|
||||
return this.isOpen && this.isSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,67 +15,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AccordionComponent } from './accordion.component';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('AccordionComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<AccordionComponent>;
|
||||
let component: AccordionComponent;
|
||||
let componentGroup1: AccordionGroupComponent;
|
||||
let componentGroup2: AccordionGroupComponent;
|
||||
let componentGroup3: AccordionGroupComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AccordionComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AccordionComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.groups = [];
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add the AccordionGroup', () => {
|
||||
component.addGroup(componentGroup1);
|
||||
expect(component.groups.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should close all the other group', () => {
|
||||
componentGroup1 = new AccordionGroupComponent(component);
|
||||
componentGroup2 = new AccordionGroupComponent(component);
|
||||
componentGroup3 = new AccordionGroupComponent(component);
|
||||
componentGroup1.isOpen = false;
|
||||
componentGroup2.isOpen = true;
|
||||
componentGroup3.isOpen = false;
|
||||
|
||||
expect(component.groups[0].isOpen).toBeFalsy();
|
||||
expect(component.groups[1].isOpen).toBeTruthy();
|
||||
expect(component.groups[2].isOpen).toBeFalsy();
|
||||
|
||||
componentGroup1.isOpen = true;
|
||||
|
||||
expect(component.groups[0].isOpen).toBeTruthy();
|
||||
expect(component.groups[1].isOpen).toBeFalsy();
|
||||
expect(component.groups[2].isOpen).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should remove the AccordionGroup', () => {
|
||||
component.addGroup(componentGroup1);
|
||||
component.removeGroup(componentGroup1);
|
||||
expect(component.groups.length).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { AccordionGroupComponent } from './accordion-group.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-accordion',
|
||||
@@ -28,25 +27,4 @@ import { AccordionGroupComponent } from './accordion-group.component';
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AccordionComponent {
|
||||
groups: Array<AccordionGroupComponent> = [];
|
||||
|
||||
addGroup(group: AccordionGroupComponent): void {
|
||||
this.groups.push(group);
|
||||
}
|
||||
|
||||
closeOthers(openGroup: AccordionGroupComponent): void {
|
||||
this.groups.forEach((group: AccordionGroupComponent) => {
|
||||
if (group !== openGroup) {
|
||||
group.isOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeGroup(group: AccordionGroupComponent): void {
|
||||
const index = this.groups.indexOf(group);
|
||||
if (index !== -1) {
|
||||
this.groups.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
export class AccordionComponent {}
|
||||
|
||||
@@ -36,6 +36,9 @@ import { AccordionComponent } from './accordion.component';
|
||||
exports: [
|
||||
AccordionComponent,
|
||||
AccordionGroupComponent
|
||||
],
|
||||
providers: [
|
||||
AccordionComponent
|
||||
]
|
||||
})
|
||||
export class CollapsableModule {}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
{{comment.message}}
|
||||
</div>
|
||||
<div matLine id="comment-time" class="adf-comment-message-time">
|
||||
{{transformDate(comment.created)}}
|
||||
{{ comment.created | adfTimeAgo: currentLocale }}
|
||||
</div>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
*/
|
||||
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { CommentModel, UserProcessModel } from '../models';
|
||||
import { CommentListComponent } from './comment-list.component';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { EcmUserService } from '../userinfo/services/ecm-user.service';
|
||||
import { PeopleProcessService } from '../services/people-process.service';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
const testUser: UserProcessModel = new UserProcessModel({
|
||||
id: '1',
|
||||
@@ -30,25 +31,25 @@ const testUser: UserProcessModel = new UserProcessModel({
|
||||
lastName: 'User',
|
||||
email: 'tu@domain.com'
|
||||
});
|
||||
const testDate = new Date();
|
||||
|
||||
const processCommentOne: CommentModel = new CommentModel({
|
||||
id: 1,
|
||||
message: 'Test Comment',
|
||||
created: testDate.toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: testUser
|
||||
});
|
||||
|
||||
const processCommentTwo: CommentModel = new CommentModel({
|
||||
id: 2,
|
||||
message: '2nd Test Comment',
|
||||
created: new Date().toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: testUser
|
||||
});
|
||||
|
||||
const contentCommentUserPictureDefined: CommentModel = new CommentModel({
|
||||
id: 2,
|
||||
message: '2nd Test Comment',
|
||||
created: new Date().toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: {
|
||||
enabled: true,
|
||||
firstName: 'some',
|
||||
@@ -64,7 +65,7 @@ const contentCommentUserPictureDefined: CommentModel = new CommentModel({
|
||||
const processCommentUserPictureDefined: CommentModel = new CommentModel({
|
||||
id: 2,
|
||||
message: '2nd Test Comment',
|
||||
created: new Date().toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: {
|
||||
id: '1',
|
||||
firstName: 'Test',
|
||||
@@ -77,7 +78,7 @@ const processCommentUserPictureDefined: CommentModel = new CommentModel({
|
||||
const contentCommentUserNoPictureDefined: CommentModel = new CommentModel({
|
||||
id: 2,
|
||||
message: '2nd Test Comment',
|
||||
created: new Date().toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: {
|
||||
enabled: true,
|
||||
firstName: 'some',
|
||||
@@ -92,7 +93,7 @@ const contentCommentUserNoPictureDefined: CommentModel = new CommentModel({
|
||||
const processCommentUserNoPictureDefined: CommentModel = new CommentModel({
|
||||
id: 2,
|
||||
message: '2nd Test Comment',
|
||||
created: new Date().toDateString(),
|
||||
created: new Date(),
|
||||
createdBy: {
|
||||
id: '1',
|
||||
firstName: 'Test',
|
||||
@@ -109,41 +110,35 @@ describe('CommentListComponent', () => {
|
||||
let ecmUserService: EcmUserService;
|
||||
let peopleProcessService: PeopleProcessService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
CommentListComponent
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
DatePipe,
|
||||
PeopleProcessService,
|
||||
EcmUserService
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(CommentListComponent);
|
||||
ecmUserService = TestBed.get(EcmUserService);
|
||||
peopleProcessService = TestBed.get(PeopleProcessService);
|
||||
commentList = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(async(() => {
|
||||
ecmUserService = TestBed.get(EcmUserService);
|
||||
spyOn(ecmUserService, 'getUserProfileImage').and.returnValue('content-user-image');
|
||||
|
||||
peopleProcessService = TestBed.get(PeopleProcessService);
|
||||
spyOn(peopleProcessService, 'getUserImage').and.returnValue('process-user-image');
|
||||
|
||||
fixture = TestBed.createComponent(CommentListComponent);
|
||||
commentList = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(ecmUserService, 'getUserProfileImage').and.returnValue('content-user-image');
|
||||
spyOn(peopleProcessService, 'getUserImage').and.returnValue('process-user-image');
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should emit row click event', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
commentList.comments = [Object.assign({}, processCommentOne)];
|
||||
|
||||
commentList.clickRow.subscribe(selectedComment => {
|
||||
expect(selectedComment.id).toEqual(1);
|
||||
expect(selectedComment.message).toEqual('Test Comment');
|
||||
expect(selectedComment.createdBy).toEqual(testUser);
|
||||
expect(selectedComment.created).toEqual(testDate.toDateString());
|
||||
expect(selectedComment.isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -156,8 +151,10 @@ describe('CommentListComponent', () => {
|
||||
|
||||
it('should deselect the previous selected comment when a new one is clicked', async(() => {
|
||||
processCommentOne.isSelected = true;
|
||||
commentList.selectedComment = processCommentOne;
|
||||
commentList.comments = [processCommentOne, processCommentTwo];
|
||||
let commentOne = Object.assign({}, processCommentOne);
|
||||
let commentTwo = Object.assign({}, processCommentTwo);
|
||||
commentList.selectedComment = commentOne;
|
||||
commentList.comments = [commentOne, commentTwo];
|
||||
|
||||
commentList.clickRow.subscribe(selectedComment => {
|
||||
fixture.detectChanges();
|
||||
@@ -182,7 +179,7 @@ describe('CommentListComponent', () => {
|
||||
}));
|
||||
|
||||
it('should show comment message when input is given', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
commentList.comments = [Object.assign({}, processCommentOne)];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
@@ -194,7 +191,7 @@ describe('CommentListComponent', () => {
|
||||
}));
|
||||
|
||||
it('should show comment user when input is given', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
commentList.comments = [Object.assign({}, processCommentOne)];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
@@ -205,42 +202,35 @@ describe('CommentListComponent', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show comment date time when input is given', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
fixture.detectChanges();
|
||||
it('comment date time should start with few seconds ago when comment date is few seconds ago', async(() => {
|
||||
let commenFewSecond = Object.assign({}, processCommentOne);
|
||||
commenFewSecond.created = new Date();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
let elements = fixture.nativeElement.querySelectorAll('#comment-time');
|
||||
expect(elements.length).toBe(1);
|
||||
expect(elements[0].innerText).toBe(commentList.transformDate(testDate.toDateString()));
|
||||
expect(fixture.nativeElement.querySelector('#comment-time:empty')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('comment date time should start with Today when comment date is today', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
commentList.comments = [commenFewSecond];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
element = fixture.nativeElement.querySelector('#comment-time');
|
||||
expect(element.innerText).toContain('Today');
|
||||
expect(element.innerText).toContain('a few seconds ago');
|
||||
});
|
||||
}));
|
||||
|
||||
it('comment date time should start with Yesterday when comment date is yesterday', async(() => {
|
||||
processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000));
|
||||
commentList.comments = [processCommentOne];
|
||||
let commentOld = Object.assign({}, processCommentOne);
|
||||
commentOld.created = new Date((Date.now() - 24 * 3600 * 1000));
|
||||
commentList.comments = [commentOld];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
element = fixture.nativeElement.querySelector('#comment-time');
|
||||
expect(element.innerText).toContain('Yesterday');
|
||||
expect(element.innerText).toContain('a day ago');
|
||||
});
|
||||
}));
|
||||
|
||||
it('comment date time should not start with Today/Yesterday when comment date is before yesterday', async(() => {
|
||||
processCommentOne.created = new Date((Date.now() - 24 * 3600 * 1000 * 2));
|
||||
commentList.comments = [processCommentOne];
|
||||
let commentOld = Object.assign({}, processCommentOne);
|
||||
commentOld.created = new Date((Date.now() - 24 * 3600 * 1000 * 2));
|
||||
commentList.comments = [commentOld];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
@@ -251,7 +241,7 @@ describe('CommentListComponent', () => {
|
||||
}));
|
||||
|
||||
it('should show user icon when input is given', async(() => {
|
||||
commentList.comments = [processCommentOne];
|
||||
commentList.comments = [Object.assign({}, processCommentOne)];
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angu
|
||||
import { CommentModel } from '../models/comment.model';
|
||||
import { EcmUserService } from '../userinfo/services/ecm-user.service';
|
||||
import { PeopleProcessService } from '../services/people-process.service';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-comment-list',
|
||||
@@ -40,8 +40,14 @@ export class CommentListComponent {
|
||||
|
||||
selectedComment: CommentModel;
|
||||
|
||||
constructor(private datePipe: DatePipe, public peopleProcessService: PeopleProcessService,
|
||||
public ecmUserService: EcmUserService) {
|
||||
currentLocale;
|
||||
|
||||
constructor(public peopleProcessService: PeopleProcessService,
|
||||
public ecmUserService: EcmUserService,
|
||||
public userPreferenceService: UserPreferencesService) {
|
||||
userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
|
||||
this.currentLocale = locale;
|
||||
});
|
||||
}
|
||||
|
||||
selectComment(comment: CommentModel): void {
|
||||
@@ -78,23 +84,6 @@ export class CommentListComponent {
|
||||
}
|
||||
}
|
||||
|
||||
transformDate(aDate: string): string {
|
||||
let formattedDate: string;
|
||||
let givenDate = Number.parseInt(this.datePipe.transform(aDate, 'yMMdd'));
|
||||
let today = Number.parseInt(this.datePipe.transform(Date.now(), 'yMMdd'));
|
||||
if (givenDate === today) {
|
||||
formattedDate = 'Today, ' + this.datePipe.transform(aDate, 'hh:mm a');
|
||||
} else {
|
||||
let yesterday = Number.parseInt(this.datePipe.transform(Date.now() - 24 * 3600 * 1000, 'yMMdd'));
|
||||
if (givenDate === yesterday) {
|
||||
formattedDate = 'Yesterday, ' + this.datePipe.transform(aDate, 'hh:mm a');
|
||||
} else {
|
||||
formattedDate = this.datePipe.transform(aDate, 'MMM dd y, hh:mm a');
|
||||
}
|
||||
}
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
private isAContentUsers(user: any): boolean {
|
||||
return user.avatarId;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,10 @@ import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { CommentProcessService } from '../services/comment-process.service';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { PeopleProcessService } from '../services/people-process.service';
|
||||
import { CommentListComponent } from './comment-list.component';
|
||||
import { CommentsComponent } from './comments.component';
|
||||
import { CommentContentService } from '../services/comment-content.service';
|
||||
import { EcmUserService } from '../userinfo/services/ecm-user.service';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('CommentsComponent', () => {
|
||||
|
||||
@@ -38,25 +36,10 @@ describe('CommentsComponent', () => {
|
||||
let commentProcessService: CommentProcessService;
|
||||
let commentContentService: CommentContentService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
],
|
||||
declarations: [
|
||||
CommentsComponent,
|
||||
CommentListComponent
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
DatePipe,
|
||||
PeopleProcessService,
|
||||
CommentProcessService,
|
||||
CommentContentService,
|
||||
EcmUserService
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CommentsComponent);
|
||||
@@ -87,7 +70,10 @@ describe('CommentsComponent', () => {
|
||||
message: 'Test Comment',
|
||||
createdBy: {id: '999'}
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should load comments when taskId specified', () => {
|
||||
|
||||
@@ -22,12 +22,14 @@ import { MaterialModule } from '../material.module';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { DataColumnModule } from '../data-column/data-column.module';
|
||||
import { DataTableModule } from '../datatable/datatable.module';
|
||||
import { PipeModule } from '../pipes/pipe.module';
|
||||
|
||||
import { CommentListComponent } from './comment-list.component';
|
||||
import { CommentsComponent } from './comments.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
PipeModule,
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
FormsModule,
|
||||
|
||||
@@ -101,31 +101,46 @@ describe('ContextMenuHolderComponent', () => {
|
||||
});
|
||||
|
||||
describe('onMenuItemClick()', () => {
|
||||
const menuItem = {
|
||||
model: {
|
||||
disabled: false
|
||||
},
|
||||
subject: {
|
||||
next: (val) => val
|
||||
}
|
||||
};
|
||||
|
||||
const event = {
|
||||
preventDefault: () => null,
|
||||
stopImmediatePropagation: () => null
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(menuItem.subject, 'next');
|
||||
});
|
||||
|
||||
it('should emit when link is not disabled', () => {
|
||||
const menuItem = {
|
||||
model: {
|
||||
disabled: false
|
||||
},
|
||||
subject: {
|
||||
next: (val) => val
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(menuItem.subject, 'next');
|
||||
|
||||
const event = {
|
||||
preventDefault: () => null,
|
||||
stopImmediatePropagation: () => null
|
||||
};
|
||||
|
||||
component.onMenuItemClick(<any> event, menuItem);
|
||||
|
||||
expect(menuItem.subject.next).toHaveBeenCalledWith(menuItem);
|
||||
});
|
||||
|
||||
it('should not emit when link is disabled', () => {
|
||||
const menuItem = {
|
||||
model: {
|
||||
disabled: false
|
||||
},
|
||||
subject: {
|
||||
next: (val) => val
|
||||
}
|
||||
};
|
||||
|
||||
spyOn(menuItem.subject, 'next');
|
||||
|
||||
const event = {
|
||||
preventDefault: () => null,
|
||||
stopImmediatePropagation: () => null
|
||||
};
|
||||
|
||||
menuItem.model.disabled = true;
|
||||
component.onMenuItemClick(<any> event, menuItem);
|
||||
|
||||
|
||||
@@ -29,16 +29,15 @@ import { ContextMenuService } from './context-menu.service';
|
||||
template: `
|
||||
<button mat-button [matMenuTriggerFor]="contextMenu"></button>
|
||||
<mat-menu #contextMenu="matMenu" class="context-menu">
|
||||
<button
|
||||
*ngFor="let link of links"
|
||||
mat-menu-item
|
||||
(click)="onMenuItemClick($event, link)"
|
||||
[attr.disabled]="link.model?.disabled || undefined">
|
||||
<mat-icon *ngIf="showIcons && link.model?.icon">
|
||||
{{ link.model?.icon }}
|
||||
</mat-icon>
|
||||
{{ (link.title || link.model?.title) | translate}}
|
||||
</button>
|
||||
<ng-container *ngFor="let link of links">
|
||||
<button *ngIf="link.model?.visible"
|
||||
mat-menu-item
|
||||
[disabled]="link.model?.disabled"
|
||||
(click)="onMenuItemClick($event, link)">
|
||||
<mat-icon *ngIf="showIcons && link.model?.icon">{{ link.model.icon }}</mat-icon>
|
||||
{{ (link.title || link.model?.title) | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
`
|
||||
})
|
||||
@@ -47,9 +46,7 @@ export class ContextMenuHolderComponent implements OnInit, OnDestroy {
|
||||
|
||||
private mouseLocation: { left: number, top: number } = {left: 0, top: 0};
|
||||
private menuElement = null;
|
||||
private openSubscription: Subscription;
|
||||
private closeSubscription: Subscription;
|
||||
private contextSubscription: Subscription;
|
||||
private subscriptions: Subscription[] = [];
|
||||
private contextMenuListenerFn: () => void;
|
||||
|
||||
@Input()
|
||||
@@ -68,7 +65,7 @@ export class ContextMenuHolderComponent implements OnInit, OnDestroy {
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
if (this.mdMenuElement) {
|
||||
this.setPositionAfterCDKrecalculation();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,33 +77,36 @@ export class ContextMenuHolderComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.contextSubscription = this.contextMenuService.show.subscribe(e => this.showMenu(e.event, e.obj));
|
||||
this.subscriptions.push(
|
||||
this.contextMenuService.show.subscribe(e => this.showMenu(e.event, e.obj)),
|
||||
|
||||
this.openSubscription = this.menuTrigger.onMenuOpen.subscribe(() => {
|
||||
const container = this.overlayContainer.getContainerElement();
|
||||
if (container) {
|
||||
this.contextMenuListenerFn = this.renderer.listen(container, 'contextmenu', (e: Event) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
this.menuElement = this.getContextMenuElement();
|
||||
});
|
||||
this.menuTrigger.onMenuOpen.subscribe(() => {
|
||||
const container = this.overlayContainer.getContainerElement();
|
||||
if (container) {
|
||||
this.contextMenuListenerFn = this.renderer.listen(container, 'contextmenu', (e: Event) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
this.menuElement = this.getContextMenuElement();
|
||||
}),
|
||||
|
||||
this.closeSubscription = this.menuTrigger.onMenuClose.subscribe(() => {
|
||||
this.menuElement = null;
|
||||
if (this.contextMenuListenerFn) {
|
||||
this.contextMenuListenerFn();
|
||||
}
|
||||
});
|
||||
this.menuTrigger.onMenuClose.subscribe(() => {
|
||||
this.menuElement = null;
|
||||
if (this.contextMenuListenerFn) {
|
||||
this.contextMenuListenerFn();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.contextMenuListenerFn) {
|
||||
this.contextMenuListenerFn();
|
||||
}
|
||||
this.contextSubscription.unsubscribe();
|
||||
this.openSubscription.unsubscribe();
|
||||
this.closeSubscription.unsubscribe();
|
||||
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
|
||||
this.menuElement = null;
|
||||
}
|
||||
|
||||
@@ -132,16 +132,10 @@ export class ContextMenuHolderComponent implements OnInit, OnDestroy {
|
||||
this.menuTrigger.openMenu();
|
||||
|
||||
if (this.mdMenuElement) {
|
||||
this.setPositionAfterCDKrecalculation();
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
|
||||
setPositionAfterCDKrecalculation() {
|
||||
setTimeout(() => {
|
||||
this.setPosition();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
get mdMenuElement() {
|
||||
return this.menuElement;
|
||||
}
|
||||
@@ -153,22 +147,24 @@ export class ContextMenuHolderComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
private setPosition() {
|
||||
if (this.mdMenuElement.clientWidth + this.mouseLocation.left > this.viewport.getViewportRect().width) {
|
||||
this.menuTrigger.menu.xPosition = 'before';
|
||||
this.mdMenuElement.parentElement.style.left = this.mouseLocation.left - this.mdMenuElement.clientWidth + 'px';
|
||||
} else {
|
||||
this.menuTrigger.menu.xPosition = 'after';
|
||||
this.mdMenuElement.parentElement.style.left = this.locationCss().left;
|
||||
}
|
||||
private updatePosition() {
|
||||
setTimeout(() => {
|
||||
if (this.mdMenuElement.clientWidth + this.mouseLocation.left > this.viewport.getViewportRect().width) {
|
||||
this.menuTrigger.menu.xPosition = 'before';
|
||||
this.mdMenuElement.parentElement.style.left = this.mouseLocation.left - this.mdMenuElement.clientWidth + 'px';
|
||||
} else {
|
||||
this.menuTrigger.menu.xPosition = 'after';
|
||||
this.mdMenuElement.parentElement.style.left = this.locationCss().left;
|
||||
}
|
||||
|
||||
if (this.mdMenuElement.clientHeight + this.mouseLocation.top > this.viewport.getViewportRect().height) {
|
||||
this.menuTrigger.menu.yPosition = 'above';
|
||||
this.mdMenuElement.parentElement.style.top = this.mouseLocation.top - this.mdMenuElement.clientHeight + 'px';
|
||||
} else {
|
||||
this.menuTrigger.menu.yPosition = 'below';
|
||||
this.mdMenuElement.parentElement.style.top = this.locationCss().top;
|
||||
}
|
||||
if (this.mdMenuElement.clientHeight + this.mouseLocation.top > this.viewport.getViewportRect().height) {
|
||||
this.menuTrigger.menu.yPosition = 'above';
|
||||
this.mdMenuElement.parentElement.style.top = this.mouseLocation.top - this.mdMenuElement.clientHeight + 'px';
|
||||
} else {
|
||||
this.menuTrigger.menu.yPosition = 'below';
|
||||
this.mdMenuElement.parentElement.style.top = this.locationCss().top;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private getContextMenuElement() {
|
||||
|
||||
@@ -25,9 +25,11 @@ import { ContextMenuService } from './context-menu.service';
|
||||
selector: '[adf-context-menu], [context-menu]'
|
||||
})
|
||||
export class ContextMenuDirective {
|
||||
/** Items for the menu. */
|
||||
@Input('context-menu')
|
||||
links: any[];
|
||||
|
||||
/** Is the menu enabled? */
|
||||
@Input('context-menu-enabled')
|
||||
enabled: boolean = false;
|
||||
|
||||
|
||||
+25
-9
@@ -17,11 +17,13 @@
|
||||
|
||||
import { CommonModule, DatePipe } from '@angular/common';
|
||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { APP_INITIALIZER, NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
|
||||
import { MaterialModule } from './material.module';
|
||||
import { AboutModule } from './about/about.module';
|
||||
import { AppConfigModule } from './app-config/app-config.module';
|
||||
import { CardViewModule } from './card-view/card-view.module';
|
||||
import { CollapsableModule } from './collapsable/collapsable.module';
|
||||
@@ -38,8 +40,9 @@ import { UserInfoModule } from './userinfo/userinfo.module';
|
||||
import { ViewerModule } from './viewer/viewer.module';
|
||||
import { FormModule } from './form/form.module';
|
||||
import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module';
|
||||
import { SideBarActionModule } from './sidebar/sidebar-action.module';
|
||||
import { CommentsModule } from './comments/comments.module';
|
||||
import { ButtonsMenuModule } from './buttons-menu/buttons-menu.module';
|
||||
import { TemplateModule } from './templates/template.module';
|
||||
|
||||
import { DirectiveModule } from './directives/directive.module';
|
||||
import { PipeModule } from './pipes/pipe.module';
|
||||
@@ -79,6 +82,7 @@ import { UploadService } from './services/upload.service';
|
||||
import { UserPreferencesService } from './services/user-preferences.service';
|
||||
import { SearchConfigurationService } from './services/search-configuration.service';
|
||||
import { startupServiceFactory } from './services/startup-service-factory';
|
||||
import { SortingPickerModule } from './sorting-picker/sorting-picker.module';
|
||||
|
||||
export function createTranslateLoader(http: HttpClient, logService: LogService) {
|
||||
return new TranslateLoaderService(http, logService);
|
||||
@@ -126,9 +130,9 @@ export function providers() {
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AboutModule,
|
||||
ViewerModule,
|
||||
SidenavLayoutModule,
|
||||
SideBarActionModule,
|
||||
PipeModule,
|
||||
CommonModule,
|
||||
DirectiveModule,
|
||||
@@ -151,17 +155,20 @@ export function providers() {
|
||||
InfoDrawerModule,
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
ButtonsMenuModule,
|
||||
TemplateModule,
|
||||
TranslateModule.forChild({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: (createTranslateLoader),
|
||||
deps: [HttpClient, LogService]
|
||||
}
|
||||
})
|
||||
}),
|
||||
SortingPickerModule
|
||||
],
|
||||
exports: [
|
||||
AboutModule,
|
||||
ViewerModule,
|
||||
SideBarActionModule,
|
||||
SidenavLayoutModule,
|
||||
PipeModule,
|
||||
CommonModule,
|
||||
@@ -185,7 +192,10 @@ export function providers() {
|
||||
InfoDrawerModule,
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
ButtonsMenuModule,
|
||||
TemplateModule,
|
||||
SortingPickerModule
|
||||
]
|
||||
})
|
||||
export class CoreModuleLazy {
|
||||
@@ -193,8 +203,8 @@ export class CoreModuleLazy {
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AboutModule,
|
||||
ViewerModule,
|
||||
SideBarActionModule,
|
||||
SidenavLayoutModule,
|
||||
PipeModule,
|
||||
CommonModule,
|
||||
@@ -218,17 +228,20 @@ export class CoreModuleLazy {
|
||||
InfoDrawerModule,
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
ButtonsMenuModule,
|
||||
TemplateModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: (createTranslateLoader),
|
||||
deps: [HttpClient, LogService]
|
||||
}
|
||||
})
|
||||
}),
|
||||
SortingPickerModule
|
||||
],
|
||||
exports: [
|
||||
AboutModule,
|
||||
ViewerModule,
|
||||
SideBarActionModule,
|
||||
SidenavLayoutModule,
|
||||
PipeModule,
|
||||
CommonModule,
|
||||
@@ -252,7 +265,10 @@ export class CoreModuleLazy {
|
||||
InfoDrawerModule,
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
ButtonsMenuModule,
|
||||
TemplateModule,
|
||||
SortingPickerModule
|
||||
],
|
||||
providers: [
|
||||
...providers(),
|
||||
|
||||
@@ -15,18 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { DataColumnListComponent } from './data-column-list.component';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('DataColumnListComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DataColumnListComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
const fixture = TestBed.createComponent(DataColumnListComponent);
|
||||
|
||||
@@ -15,18 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { DataColumnComponent } from './data-column.component';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('DataColumnListComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DataColumnComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
const fixture = TestBed.createComponent(DataColumnComponent);
|
||||
@@ -34,7 +32,7 @@ describe('DataColumnListComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('sould setup screen reader title for thumbnails', () => {
|
||||
it('should setup screen reader title for thumbnails', () => {
|
||||
const component = new DataColumnComponent();
|
||||
component.key = '$thumbnail';
|
||||
expect(component.srTitle).toBeFalsy();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<div
|
||||
*ngIf="data" class="full-width"
|
||||
[class.adf-data-table-card]="display === 'gallery'"
|
||||
[class.adf-data-table]="display === 'list'">
|
||||
[class.adf-data-table]="display === 'list'"
|
||||
[class.adf-data-table--empty]="isEmpty()">
|
||||
<div *ngIf="isHeaderVisible()" class="adf-datatable-header">
|
||||
<div class="adf-datatable-row" *ngIf="display === 'list'">
|
||||
<!-- Actions (left) -->
|
||||
@@ -54,10 +55,10 @@
|
||||
[ngStyle]="rowStyle"
|
||||
[ngClass]="getRowStyle(row)"
|
||||
(keyup)="onRowKeyUp(row, $event)">
|
||||
|
||||
<!-- Actions (left) -->
|
||||
<div *ngIf="actions && actionsPosition === 'left'" class="adf-datatable-table-cell">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu"
|
||||
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
|
||||
[attr.id]="'action_menu_left_' + idx"
|
||||
[attr.data-automation-id]="'action_menu_' + idx">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
@@ -91,22 +92,26 @@
|
||||
<div *ngIf="!col.template" class="cell-container">
|
||||
<ng-container [ngSwitch]="col.type">
|
||||
<div *ngSwitchCase="'image'" class="cell-value">
|
||||
<mat-icon *ngIf="isIconValue(row, col)">{{ asIconValue(row, col) }}</mat-icon>
|
||||
<mat-icon class="adf-datatable-selected"
|
||||
*ngIf="!isIconValue(row, col) && row.isSelected" svgIcon="selected">
|
||||
<mat-icon *ngIf="isIconValue(row, col); else no_iconvalue">{{ asIconValue(row, col) }}
|
||||
</mat-icon>
|
||||
|
||||
<img *ngIf="!isIconValue(row, col) && !row.isSelected"
|
||||
alt="{{ iconAltTextKey(data.getValue(row, col)) | translate }}"
|
||||
src="{{ data.getValue(row, col) }}"
|
||||
(error)="onImageLoadingError($event, row.obj.entry.content.mimeType)">
|
||||
<ng-template #no_iconvalue>
|
||||
<mat-icon class="adf-datatable-selected"
|
||||
*ngIf="row.isSelected; else no_selected_row" svgIcon="selected">
|
||||
</mat-icon>
|
||||
<ng-template #no_selected_row>
|
||||
<img
|
||||
alt="{{ iconAltTextKey(data.getValue(row, col)) | translate }}"
|
||||
src="{{ data.getValue(row, col) }}"
|
||||
(error)="onImageLoadingError($event, row)">
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngSwitchCase="'icon'" class="cell-value">
|
||||
<span class="sr-only">{{ iconAltTextKey(data.getValue(row, col)) | translate }}</span>
|
||||
<mat-icon>{{ data.getValue(row, col) }}</mat-icon>
|
||||
</div>
|
||||
<div *ngSwitchCase="'date'" class="cell-value"
|
||||
[attr.data-automation-id]="'date_' + data.getValue(row, col)">
|
||||
[attr.data-automation-id]="'date_' + (data.getValue(row, col) | date: 'medium') ">
|
||||
<adf-date-cell
|
||||
[data]="data"
|
||||
[column]="col"
|
||||
@@ -158,6 +163,7 @@
|
||||
<div *ngIf="actions && actionsPosition === 'right'"
|
||||
class="adf-datatable-table-cell alfresco-datatable__actions-cell">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menu"
|
||||
[title]="'ADF-DATATABLE.CONTENT-ACTIONS.TOOLTIP' | translate"
|
||||
[attr.id]="'action_menu_right_' + idx"
|
||||
[attr.data-automation-id]="'action_menu_' + idx">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
@@ -200,7 +206,7 @@
|
||||
<div *ngIf="loading"
|
||||
[class.adf-datatable-row]="display === 'list'"
|
||||
[class.adf-data-table-card-loading]="display === 'gallery'">
|
||||
<div class="adf-datatable-table-cell" >
|
||||
<div class="adf-datatable-table-cell">
|
||||
<ng-template *ngIf="loadingTemplate"
|
||||
ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="loadingTemplate">
|
||||
|
||||
@@ -14,14 +14,9 @@
|
||||
$data-table-selection-color: mat-color($background, 'selected-button') !default;
|
||||
$data-table-dividers: 1px solid $data-table-divider-color !default;
|
||||
$data-table-row-height: 56px !default;
|
||||
// $data-table-last-row-height: 56px !default;
|
||||
// $data-table-header-height: 56px !default;
|
||||
$data-table-column-spacing: 36px !default;
|
||||
$data-table-column-padding: $data-table-column-spacing / 2;
|
||||
// $data-table-card-header-height: 64px !default;
|
||||
// $data-table-card-title-top: 20px !default;
|
||||
$data-table-card-padding: 24px !default;
|
||||
// $data-table-button-padding-right: 16px !default;
|
||||
$data-table-cell-top: $data-table-card-padding / 2;
|
||||
$data-table-drag-border: 1px dashed rgb(68, 138, 255);
|
||||
|
||||
@@ -170,6 +165,7 @@
|
||||
border-collapse: collapse;
|
||||
white-space: nowrap;
|
||||
font-size: $data-table-font-size;
|
||||
background-color: mat-color($background, card);
|
||||
|
||||
/* Firefox fixes */
|
||||
border-collapse: unset;
|
||||
@@ -394,6 +390,8 @@
|
||||
.ellipsis-cell {
|
||||
.cell-container {
|
||||
height: 100%;
|
||||
display: inline-grid;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cell-container > * {
|
||||
@@ -458,16 +456,36 @@
|
||||
}
|
||||
|
||||
.adf-upload__dragging {
|
||||
& > td {
|
||||
border-top: $data-table-drag-border;
|
||||
border-bottom: $data-table-drag-border;
|
||||
& > div {
|
||||
border-top: $data-table-drag-border !important;
|
||||
border-bottom: $data-table-drag-border !important;
|
||||
border-color: $data-table-drag-border;
|
||||
|
||||
&:first-child {
|
||||
border-left: $data-table-drag-border;
|
||||
border-color: $data-table-drag-border;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: $data-table-drag-border;
|
||||
border-right: $data-table-drag-border !important;
|
||||
border-color: $data-table-drag-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.adf-data-table--empty {
|
||||
@include flex-column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.adf-datatable-body {
|
||||
.adf-datatable-row {
|
||||
background-color: mat-color($background, card);
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: unset;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SimpleChange, NO_ERRORS_SCHEMA, QueryList } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { DataTableModule } from '../../datatable.module';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { DataColumn } from '../../data/data-column.model';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
import { DataSorting } from '../../data/data-sorting.model';
|
||||
import { ObjectDataColumn } from '../../data/object-datacolumn.model';
|
||||
import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
|
||||
|
||||
import { DataTableComponent } from './datatable.component';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
import { DataColumnListComponent } from '../../../data-column/data-column-list.component';
|
||||
import { DataColumnComponent } from '../../../data-column/data-column.component';
|
||||
|
||||
class FakeDataRow implements DataRow {
|
||||
isDropTarget = false;
|
||||
isSelected = true;
|
||||
hasValue(key: any) {
|
||||
return true;
|
||||
}
|
||||
getValue() {
|
||||
return '1';
|
||||
}
|
||||
imageErrorResolver() {
|
||||
return './assets/images/ft_ic_miscellaneous.svg';
|
||||
}
|
||||
}
|
||||
|
||||
describe('DataTable', () => {
|
||||
|
||||
@@ -35,15 +49,12 @@ describe('DataTable', () => {
|
||||
let dataTable: DataTableComponent;
|
||||
let element: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreTestingModule
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DataTableComponent);
|
||||
@@ -51,6 +62,45 @@ describe('DataTable', () => {
|
||||
element = fixture.debugElement.nativeElement;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should preserve the historical selection order', () => {
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[{ id: 0 }, { id: 1 }, { id: 2 }],
|
||||
[ new ObjectDataColumn({ key: 'id' })]
|
||||
);
|
||||
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.selectRow(rows[2], true);
|
||||
dataTable.selectRow(rows[0], true);
|
||||
dataTable.selectRow(rows[1], true);
|
||||
|
||||
const selection = dataTable.selection;
|
||||
expect(selection[0].getValue('id')).toBe(2);
|
||||
expect(selection[1].getValue('id')).toBe(0);
|
||||
expect(selection[2].getValue('id')).toBe(1);
|
||||
});
|
||||
|
||||
it('should update schema if columns change', fakeAsync(() => {
|
||||
|
||||
dataTable.columnList = new DataColumnListComponent();
|
||||
dataTable.columnList.columns = new QueryList<DataColumnComponent>();
|
||||
dataTable.data = new ObjectDataTableAdapter([], []);
|
||||
|
||||
spyOn(dataTable.data, 'setColumns').and.callThrough();
|
||||
|
||||
dataTable.ngAfterContentInit();
|
||||
dataTable.columnList.columns.reset([new DataColumnComponent()]);
|
||||
dataTable.columnList.columns.notifyOnChanges();
|
||||
|
||||
tick(100);
|
||||
|
||||
expect(dataTable.data.setColumns).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should use the cardview style if cardview is true', () => {
|
||||
let newData = new ObjectDataTableAdapter(
|
||||
[
|
||||
@@ -189,6 +239,46 @@ describe('DataTable', () => {
|
||||
expect(element.querySelector('[data-automation-id="text_FAKE"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should set rows to the data when rows defined', () => {
|
||||
const dataRows =
|
||||
[
|
||||
{ name: 'test1' },
|
||||
{ name: 'test2' },
|
||||
{ name: 'test3' },
|
||||
{ name: 'test4' }
|
||||
];
|
||||
dataTable.data = new ObjectDataTableAdapter([],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const rows = dataTable.data.getRows();
|
||||
expect(rows[0].getValue('name')).toEqual('test1');
|
||||
expect(rows[1].getValue('name')).toEqual('test2');
|
||||
});
|
||||
|
||||
it('should set custom sort order', () => {
|
||||
const dataSortObj = new DataSorting('dummayName', 'asc');
|
||||
const dataRows =
|
||||
[
|
||||
{ name: 'test1' },
|
||||
{ name: 'test2' },
|
||||
{ name: 'test3' },
|
||||
{ name: 'test4' }
|
||||
];
|
||||
dataTable.sorting = [ 'dummayName', 'asc' ];
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
const dataSort = dataTable.data.getSorting();
|
||||
expect(dataSort).toEqual(dataSortObj);
|
||||
});
|
||||
|
||||
it('should reset selection on mode change', () => {
|
||||
spyOn(dataTable, 'resetSelection').and.callThrough();
|
||||
|
||||
@@ -213,25 +303,89 @@ describe('DataTable', () => {
|
||||
expect(dataTable.resetSelection).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should select only one row with [single] selection mode', () => {
|
||||
it('should select the row where isSelected is true', () => {
|
||||
dataTable.rows = [
|
||||
{ name: 'TEST1' },
|
||||
{ name: 'FAKE2' },
|
||||
{ name: 'TEST2', isSelected : true },
|
||||
{ name: 'FAKE2' }];
|
||||
dataTable.data = new ObjectDataTableAdapter([],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
fixture.detectChanges();
|
||||
const rows = dataTable.data.getRows();
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
expect(rows[2].isSelected).toBeTruthy();
|
||||
expect(rows[3].isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not select any row when isSelected is not defined', () => {
|
||||
const dataRows =
|
||||
[
|
||||
{ name: 'TEST1' },
|
||||
{ name: 'FAKE2' },
|
||||
{ name: 'TEST2' }
|
||||
];
|
||||
dataTable.data = new ObjectDataTableAdapter(dataRows,
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
|
||||
dataTable.ngOnChanges({
|
||||
rows: new SimpleChange(null, dataRows, false)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const rows = dataTable.data.getRows();
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
expect(rows[2].isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should select only one row with [single] selection mode', (done) => {
|
||||
dataTable.selectionMode = 'single';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '1', isSelected: true },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
|
||||
dataTable.onRowClick(rows[1], null);
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
dataTable.rowClick.subscribe((event) => {
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.onRowClick(rows[1], new MouseEvent('click'));
|
||||
});
|
||||
|
||||
it('should select only one row with [single] selection mode and key modifier', (done) => {
|
||||
dataTable.selectionMode = 'single';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1', isSelected: true },
|
||||
{ name: '2' }
|
||||
],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
|
||||
dataTable.rowClick.subscribe((event) => {
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.onRowClick(rows[1], new MouseEvent('click', {
|
||||
metaKey: true
|
||||
}));
|
||||
});
|
||||
|
||||
it('should select only one row with [single] selection mode pressing enter key', () => {
|
||||
@@ -278,67 +432,67 @@ describe('DataTable', () => {
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not unselect the row with [single] selection mode', () => {
|
||||
it('should NOT unselect the row with [single] selection mode', (done) => {
|
||||
dataTable.selectionMode = 'single';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '1', isSelected: true },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
|
||||
dataTable.rowClick.subscribe((event) => {
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should unselect the row with [multiple] selection mode and modifier key', () => {
|
||||
it('should unselect the row with [multiple] selection mode and modifier key', (done) => {
|
||||
dataTable.selectionMode = 'multiple';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[ { name: '1' } ],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
[{ name: '1', isSelected: true }],
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
rows[0].isSelected = true;
|
||||
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
dataTable.rowClick.subscribe(() => {
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
|
||||
dataTable.onRowClick(rows[0], null);
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
|
||||
dataTable.onRowClick(rows[0], <any> { metaKey: true, preventDefault() {} });
|
||||
expect(rows[0].isSelected).toBeFalsy();
|
||||
dataTable.onRowClick(rows[0], <any> { metaKey: true, preventDefault() { } });
|
||||
});
|
||||
|
||||
it('should select multiple rows with [multiple] selection mode', () => {
|
||||
it('should select multiple rows with [multiple] selection mode and modifier key', (done) => {
|
||||
dataTable.selectionMode = 'multiple';
|
||||
dataTable.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{ name: '1' },
|
||||
{ name: '1', isSelected: true },
|
||||
{ name: '2' }
|
||||
],
|
||||
[ new ObjectDataColumn({ key: 'name'}) ]
|
||||
[new ObjectDataColumn({ key: 'name' })]
|
||||
);
|
||||
const rows = dataTable.data.getRows();
|
||||
rows[0].isSelected = true;
|
||||
|
||||
const event = new MouseEvent('click', {
|
||||
metaKey: true
|
||||
});
|
||||
|
||||
dataTable.selection.push(rows[0]);
|
||||
dataTable.ngOnChanges({});
|
||||
dataTable.onRowClick(rows[0], event);
|
||||
dataTable.rowClick.subscribe(() => {
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
dataTable.onRowClick(rows[1], event);
|
||||
|
||||
expect(rows[0].isSelected).toBeTruthy();
|
||||
expect(rows[1].isSelected).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should put actions menu to the right by default', () => {
|
||||
@@ -385,15 +539,6 @@ describe('DataTable', () => {
|
||||
expect(table.data).toEqual(jasmine.any(ObjectDataTableAdapter));
|
||||
});
|
||||
|
||||
it('should load data table on onChange', () => {
|
||||
let table = new DataTableComponent(null, null);
|
||||
let data = new ObjectDataTableAdapter([], []);
|
||||
|
||||
expect(table.data).toBeUndefined();
|
||||
table.ngOnChanges({'data': new SimpleChange('123', data, true)});
|
||||
expect(table.data).toEqual(data);
|
||||
});
|
||||
|
||||
it('should initialize with custom data', () => {
|
||||
let data = new ObjectDataTableAdapter([], []);
|
||||
dataTable.data = data;
|
||||
@@ -591,6 +736,22 @@ describe('DataTable', () => {
|
||||
expect(dataTable.isSelectAllChecked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reset selection upon data rows change', () => {
|
||||
let data = new ObjectDataTableAdapter([{}, {}, {}], []);
|
||||
|
||||
dataTable.data = data;
|
||||
dataTable.multiselect = true;
|
||||
dataTable.ngAfterContentInit();
|
||||
dataTable.onSelectAllClick(<MatCheckboxChange> { checked: true });
|
||||
|
||||
expect(dataTable.selection.every(entry => entry.isSelected));
|
||||
|
||||
data.setRows([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(dataTable.selection.every(entry => !entry.isSelected));
|
||||
});
|
||||
|
||||
it('should update rows on "select all" click', () => {
|
||||
let data = new ObjectDataTableAdapter([{}, {}, {}], []);
|
||||
let rows = data.getRows();
|
||||
@@ -720,9 +881,9 @@ describe('DataTable', () => {
|
||||
src: 'missing-image'
|
||||
}
|
||||
};
|
||||
|
||||
const row = new FakeDataRow();
|
||||
dataTable.fallbackThumbnail = '<fallback>';
|
||||
dataTable.onImageLoadingError(event);
|
||||
dataTable.onImageLoadingError(event, row);
|
||||
expect(event.target.src).toBe(dataTable.fallbackThumbnail);
|
||||
});
|
||||
|
||||
@@ -733,23 +894,12 @@ describe('DataTable', () => {
|
||||
src: originalSrc
|
||||
}
|
||||
};
|
||||
|
||||
const row = new FakeDataRow();
|
||||
dataTable.fallbackThumbnail = null;
|
||||
dataTable.onImageLoadingError(event);
|
||||
dataTable.onImageLoadingError(event, row);
|
||||
expect(event.target.src).toBe('./assets/images/ft_ic_miscellaneous.svg' );
|
||||
});
|
||||
|
||||
it('should replace image source with icon if fallback is not available and mimeType is provided', () => {
|
||||
let event = <any> {
|
||||
target: {
|
||||
src: 'missing-image'
|
||||
}
|
||||
};
|
||||
|
||||
dataTable.onImageLoadingError(event, 'image/png');
|
||||
expect(event.target.src).toBe('./assets/images/ft_ic_raster_image.svg');
|
||||
});
|
||||
|
||||
it('should not get cell tooltip when row is not provided', () => {
|
||||
const col = <DataColumn> { key: 'name', type: 'text' };
|
||||
expect(dataTable.getCellTooltip(null, col)).toBeNull();
|
||||
@@ -787,20 +937,6 @@ describe('DataTable', () => {
|
||||
expect(dataTable.getCellTooltip(row, col)).toBeNull();
|
||||
});
|
||||
|
||||
it('should cache the rows menu', () => {
|
||||
let emitted = 0;
|
||||
dataTable.showRowActionsMenu.subscribe(() => { emitted++; });
|
||||
|
||||
const column = <DataColumn> {};
|
||||
const row = <DataRow> { getValue: function (key: string) { return 'id'; } };
|
||||
|
||||
dataTable.getRowActions(row, column);
|
||||
dataTable.getRowActions(row, column);
|
||||
dataTable.getRowActions(row, column);
|
||||
|
||||
expect(emitted).toBe(1);
|
||||
});
|
||||
|
||||
it('should reset the menu cache after rows change', () => {
|
||||
let emitted = 0;
|
||||
dataTable.showRowActionsMenu.subscribe(() => { emitted++; });
|
||||
|
||||
@@ -17,19 +17,16 @@
|
||||
|
||||
import {
|
||||
AfterContentInit, Component, ContentChild, DoCheck, ElementRef, EventEmitter, Input,
|
||||
IterableDiffers, OnChanges, Output, SimpleChange, SimpleChanges, TemplateRef, ViewEncapsulation
|
||||
IterableDiffers, OnChanges, Output, SimpleChange, SimpleChanges, TemplateRef, ViewEncapsulation, OnDestroy
|
||||
} from '@angular/core';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import { Subscription, Observable, Observer } from 'rxjs/Rx';
|
||||
import { DataColumnListComponent } from '../../../data-column/data-column-list.component';
|
||||
import { DataColumn } from '../../data/data-column.model';
|
||||
import { DataRowEvent } from '../../data/data-row-event.model';
|
||||
import { DataRow } from '../../data/data-row.model';
|
||||
import { DataSorting } from '../../data/data-sorting.model';
|
||||
import { DataTableAdapter } from '../../data/datatable-adapter';
|
||||
import { ThumbnailService } from '../../../services/thumbnail.service';
|
||||
|
||||
import { ObjectDataRow } from '../../data/object-datarow.model';
|
||||
import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter';
|
||||
@@ -50,7 +47,7 @@ export enum DisplayMode {
|
||||
templateUrl: './datatable.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck {
|
||||
export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck, OnDestroy {
|
||||
|
||||
@ContentChild(DataColumnListComponent)
|
||||
columnList: DataColumnListComponent;
|
||||
@@ -67,6 +64,16 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
@Input()
|
||||
rows: any[] = [];
|
||||
|
||||
/** Define the sort order of the datatable. Possible values are :
|
||||
* [`created`, `desc`], [`created`, `asc`], [`due`, `desc`], [`due`, `asc`]
|
||||
*/
|
||||
@Input()
|
||||
sorting: any[] = [];
|
||||
|
||||
/** The columns that the datatable will show. */
|
||||
@Input()
|
||||
columns: any[] = [];
|
||||
|
||||
/** Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode,
|
||||
* you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows.
|
||||
*/
|
||||
@@ -144,6 +151,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
@Input()
|
||||
noPermission: boolean = false;
|
||||
|
||||
@Input()
|
||||
rowMenuCacheEnabled = true;
|
||||
|
||||
noContentTemplate: TemplateRef<any>;
|
||||
noPermissionTemplate: TemplateRef<any>;
|
||||
loadingTemplate: TemplateRef<any>;
|
||||
@@ -154,17 +164,16 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
private clickObserver: Observer<DataRowEvent>;
|
||||
private click$: Observable<DataRowEvent>;
|
||||
|
||||
private schema: DataColumn[] = [];
|
||||
|
||||
private differ: any;
|
||||
private rowMenuCache: object = {};
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
private singleClickStreamSub: Subscription;
|
||||
private multiClickStreamSub: Subscription;
|
||||
private dataRowsChanged: Subscription;
|
||||
|
||||
constructor(private elementRef: ElementRef,
|
||||
differs: IterableDiffers,
|
||||
private thumbnailService?: ThumbnailService) {
|
||||
differs: IterableDiffers) {
|
||||
if (differs) {
|
||||
this.differ = differs.find([]).create(null);
|
||||
}
|
||||
@@ -172,6 +181,13 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.columnList) {
|
||||
this.subscriptions.push(
|
||||
this.columnList.columns.changes.subscribe(() => {
|
||||
this.setTableSchema();
|
||||
})
|
||||
);
|
||||
}
|
||||
this.setTableSchema();
|
||||
}
|
||||
|
||||
@@ -182,6 +198,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
this.initTable();
|
||||
} else {
|
||||
this.data = changes['data'].currentValue;
|
||||
this.resetSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -199,6 +216,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
this.resetSelection();
|
||||
this.emitRowSelectionEvent('row-unselect', null);
|
||||
}
|
||||
|
||||
if (this.isPropertyChanged(changes['sorting'])) {
|
||||
this.setTableSorting(changes['sorting'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
@@ -213,7 +234,13 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
}
|
||||
|
||||
convertToRowsData(rows: any []): ObjectDataRow[] {
|
||||
return rows.map(row => new ObjectDataRow(row));
|
||||
return rows.map(row => new ObjectDataRow(row, row.isSelected));
|
||||
}
|
||||
|
||||
convertToDataSorting(sorting: any[]): DataSorting {
|
||||
if (sorting && sorting.length > 0) {
|
||||
return new DataSorting(sorting[0], sorting[1]);
|
||||
}
|
||||
}
|
||||
|
||||
private initAndSubscribeClickStream() {
|
||||
@@ -225,6 +252,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
|
||||
this.singleClickStreamSub = singleClickStream.subscribe((obj: DataRowEvent[]) => {
|
||||
let event: DataRowEvent = obj[0];
|
||||
this.handleRowSelection(event.value, <MouseEvent | KeyboardEvent> event.event);
|
||||
this.rowClick.emit(event);
|
||||
if (!event.defaultPrevented) {
|
||||
this.elementRef.nativeElement.dispatchEvent(
|
||||
@@ -258,14 +286,18 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
private unsubscribeClickStream() {
|
||||
if (this.singleClickStreamSub) {
|
||||
this.singleClickStreamSub.unsubscribe();
|
||||
this.singleClickStreamSub = null;
|
||||
}
|
||||
if (this.multiClickStreamSub) {
|
||||
this.multiClickStreamSub.unsubscribe();
|
||||
this.multiClickStreamSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
private initTable() {
|
||||
this.data = new ObjectDataTableAdapter(this.rows, this.schema);
|
||||
this.data = new ObjectDataTableAdapter(this.rows, this.columns);
|
||||
this.setTableSorting(this.sorting);
|
||||
this.resetSelection();
|
||||
this.rowMenuCache = {};
|
||||
}
|
||||
|
||||
@@ -273,29 +305,48 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
return this.data === undefined || this.data === null;
|
||||
}
|
||||
|
||||
private setTableRows(rows) {
|
||||
private setTableRows(rows: any[]) {
|
||||
if (this.data) {
|
||||
this.resetSelection();
|
||||
this.data.setRows(this.convertToRowsData(rows));
|
||||
}
|
||||
}
|
||||
|
||||
private setTableSchema() {
|
||||
if (this.columnList && this.columnList.columns) {
|
||||
this.schema = this.columnList.columns.map(c => <DataColumn> c);
|
||||
let schema = [];
|
||||
if (!this.columns || this.columns.length === 0) {
|
||||
schema = this.getSchemaFromHtml();
|
||||
} else {
|
||||
schema = this.columns.concat(this.getSchemaFromHtml());
|
||||
}
|
||||
|
||||
if (this.data && this.schema && this.schema.length > 0) {
|
||||
this.data.setColumns(this.schema);
|
||||
this.columns = schema;
|
||||
|
||||
if (this.data && this.columns && this.columns.length > 0) {
|
||||
this.data.setColumns(this.columns);
|
||||
}
|
||||
}
|
||||
|
||||
private setTableSorting(sorting) {
|
||||
if (this.data) {
|
||||
this.data.setSorting(this.convertToDataSorting(sorting));
|
||||
}
|
||||
}
|
||||
|
||||
public getSchemaFromHtml(): any {
|
||||
let schema = [];
|
||||
if (this.columnList && this.columnList.columns && this.columnList.columns.length > 0) {
|
||||
schema = this.columnList.columns.map(c => <DataColumn> c);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
onRowClick(row: DataRow, e: MouseEvent) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (row) {
|
||||
this.handleRowSelection(row, e);
|
||||
const dataRowEvent = new DataRowEvent(row, e, this);
|
||||
this.clickObserver.next(dataRowEvent);
|
||||
}
|
||||
@@ -317,7 +368,12 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
|
||||
if (this.isMultiSelectionMode()) {
|
||||
const modifier = e && (e.metaKey || e.ctrlKey);
|
||||
const newValue = modifier ? !row.isSelected : true;
|
||||
let newValue: boolean;
|
||||
if (this.selection.length === 1) {
|
||||
newValue = !row.isSelected;
|
||||
} else {
|
||||
newValue = modifier ? !row.isSelected : true;
|
||||
}
|
||||
const domEventName = newValue ? 'row-select' : 'row-unselect';
|
||||
|
||||
if (!modifier) {
|
||||
@@ -335,7 +391,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
if (rows && rows.length > 0) {
|
||||
rows.forEach(r => r.isSelected = false);
|
||||
}
|
||||
this.selection.splice(0);
|
||||
this.selection = [];
|
||||
}
|
||||
this.isSelectAllChecked = false;
|
||||
}
|
||||
@@ -424,15 +480,14 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
this.emitRowSelectionEvent(domEventName, row);
|
||||
}
|
||||
|
||||
onImageLoadingError(event: Event, mimeType?: string) {
|
||||
|
||||
onImageLoadingError(event: Event, row: DataRow) {
|
||||
if (event) {
|
||||
let element = <any> event.target;
|
||||
|
||||
if (this.fallbackThumbnail) {
|
||||
element.src = this.fallbackThumbnail;
|
||||
} else {
|
||||
element.src = this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
element.src = row.imageErrorResolver(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,6 +532,9 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
if (!this.rowMenuCache[id]) {
|
||||
let event = new DataCellEvent(row, col, []);
|
||||
this.showRowActionsMenu.emit(event);
|
||||
if (!this.rowMenuCacheEnabled) {
|
||||
return event.value.actions;
|
||||
}
|
||||
this.rowMenuCache[id] = event.value.actions;
|
||||
}
|
||||
|
||||
@@ -523,11 +581,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
}
|
||||
}
|
||||
|
||||
private selectRow(row: DataRow, value: boolean) {
|
||||
selectRow(row: DataRow, value: boolean) {
|
||||
if (row) {
|
||||
row.isSelected = value;
|
||||
const idx = this.selection.indexOf(row);
|
||||
|
||||
if (value) {
|
||||
if (idx < 0) {
|
||||
this.selection.push(row);
|
||||
@@ -551,8 +608,8 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
}
|
||||
|
||||
getSortableColumns() {
|
||||
return this.data.getColumns().filter((currentColum) => {
|
||||
return currentColum.sortable === true;
|
||||
return this.data.getColumns().filter(column => {
|
||||
return column.sortable === true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -585,4 +642,16 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck
|
||||
});
|
||||
this.elementRef.nativeElement.dispatchEvent(domEvent);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubscribeClickStream();
|
||||
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
this.subscriptions = [];
|
||||
|
||||
if (this.dataRowsChanged) {
|
||||
this.dataRowsChanged.unsubscribe();
|
||||
this.dataRowsChanged = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { DataTableCellComponent } from './datatable-cell.component';
|
||||
import { UserPreferencesService, UserPreferenceValues } from '../../../services/user-preferences.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-date-cell',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
template: `
|
||||
<ng-container>
|
||||
<span [title]="tooltip">{{value}}</span>
|
||||
<span title="{{ tooltip | date:'medium' }}" *ngIf="column?.format === 'timeAgo' else standard_date">
|
||||
{{ value | adfTimeAgo: currentLocale }}
|
||||
</span>
|
||||
</ng-container>
|
||||
<ng-template #standard_date>
|
||||
<span title="{{ tooltip | date:'medium' }}">
|
||||
{{ value | date:'medium' }}
|
||||
</span>
|
||||
</ng-template>
|
||||
`,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-date-cell' }
|
||||
})
|
||||
export class DateCellComponent extends DataTableCellComponent {}
|
||||
export class DateCellComponent extends DataTableCellComponent {
|
||||
|
||||
currentLocale;
|
||||
|
||||
constructor(userPreferenceService: UserPreferencesService) {
|
||||
super();
|
||||
|
||||
userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
|
||||
this.currentLocale = locale;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,21 +17,25 @@
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { EmptyListComponent } from './empty-list.component';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('EmptyListComponentComponent', () => {
|
||||
let component: EmptyListComponent;
|
||||
let fixture: ComponentFixture<EmptyListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
EmptyListComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmptyListComponent);
|
||||
component = fixture.componentInstance;
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(component).toBeDefined();
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ObjectDataTableAdapter } from './../../data/object-datatable-adapter';
|
||||
import { ObjectDataColumn } from './../../data/object-datacolumn.model';
|
||||
|
||||
import { LocationCellComponent } from './location-cell.component';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
|
||||
describe('LocationCellComponent', () => {
|
||||
let component: LocationCellComponent;
|
||||
@@ -29,16 +30,11 @@ describe('LocationCellComponent', () => {
|
||||
let rowData;
|
||||
let columnData;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
LocationCellComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(LocationCellComponent);
|
||||
component = fixture.componentInstance;
|
||||
}));
|
||||
@@ -69,6 +65,10 @@ describe('LocationCellComponent', () => {
|
||||
component.row = dataTableAdapter.getRows()[0];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should set displayText', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ export interface DataRow {
|
||||
isSelected: boolean;
|
||||
isDropTarget?: boolean;
|
||||
cssClass?: string;
|
||||
|
||||
hasValue(key: string): boolean;
|
||||
|
||||
getValue(key: string): any;
|
||||
|
||||
imageErrorResolver?(event: Event): any;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { ContentChild, Input } from '@angular/core';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { DataColumnListComponent } from '../../data-column/data-column-list.component';
|
||||
import { DataColumn } from './data-column.model';
|
||||
import { ObjectDataColumn } from './object-datacolumn.model';
|
||||
|
||||
export abstract class DataTableSchema {
|
||||
|
||||
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
|
||||
|
||||
/** Custom preset column schema in JSON format. */
|
||||
@Input()
|
||||
presetColumn: string;
|
||||
|
||||
columns: any;
|
||||
|
||||
private layoutPresets = {};
|
||||
|
||||
constructor(private appConfigService: AppConfigService,
|
||||
protected presetKey: string,
|
||||
protected presetsModel: any) { }
|
||||
|
||||
public createDatatableSchema(): void {
|
||||
this.loadLayoutPresets();
|
||||
if (!this.columns || this.columns.length === 0) {
|
||||
this.columns = this.mergeJsonAndHtmlSchema();
|
||||
}
|
||||
}
|
||||
|
||||
public loadLayoutPresets(): void {
|
||||
const externalSettings = this.appConfigService.get(this.presetKey, null);
|
||||
if (externalSettings) {
|
||||
this.layoutPresets = Object.assign({}, this.presetsModel, externalSettings);
|
||||
} else {
|
||||
this.layoutPresets = this.presetsModel;
|
||||
}
|
||||
}
|
||||
|
||||
public mergeJsonAndHtmlSchema(): any {
|
||||
let customSchemaColumns = [];
|
||||
customSchemaColumns = this.getSchemaFromConfig(this.presetColumn).concat(this.getSchemaFromHtml(this.columnList));
|
||||
if (customSchemaColumns.length === 0) {
|
||||
customSchemaColumns = this.getDefaultLayoutPreset();
|
||||
}
|
||||
return customSchemaColumns;
|
||||
}
|
||||
|
||||
public getSchemaFromHtml(columnList: DataColumnListComponent): any {
|
||||
let schema = [];
|
||||
if (columnList && columnList.columns && columnList.columns.length > 0) {
|
||||
schema = columnList.columns.map(c => <DataColumn> c);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
public getSchemaFromConfig(presetColoumn: string): DataColumn[] {
|
||||
return presetColoumn ? (this.layoutPresets[presetColoumn]).map(col => new ObjectDataColumn(col)) : [];
|
||||
}
|
||||
|
||||
private getDefaultLayoutPreset(): DataColumn[] {
|
||||
return (this.layoutPresets['default']).map(col => new ObjectDataColumn(col));
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,11 @@
|
||||
import { DataColumn } from './data-column.model';
|
||||
import { DataRow } from './data-row.model';
|
||||
import { DataSorting } from './data-sorting.model';
|
||||
import { Subject } from 'rxjs/Rx';
|
||||
|
||||
export interface DataTableAdapter {
|
||||
rowsChanged?: Subject<Array<DataRow>>;
|
||||
|
||||
selectedRow: DataRow;
|
||||
getRows(): Array<DataRow>;
|
||||
setRows(rows: Array<DataRow>): void;
|
||||
|
||||
@@ -35,4 +35,8 @@ export class ObjectDataRow implements DataRow {
|
||||
hasValue(key: string): boolean {
|
||||
return this.getValue(key) !== undefined;
|
||||
}
|
||||
|
||||
imageErrorResolver(event: Event): string {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
import { TimeAgoPipe } from '../../pipes';
|
||||
import { DataColumn } from './data-column.model';
|
||||
import { DataRow } from './data-row.model';
|
||||
import { ObjectDataRow } from './object-datarow.model';
|
||||
import { ObjectDataColumn } from './object-datacolumn.model';
|
||||
import { DataSorting } from './data-sorting.model';
|
||||
import { DataTableAdapter } from './datatable-adapter';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
// Simple implementation of the DataTableAdapter interface.
|
||||
export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
@@ -33,6 +31,7 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
private _columns: DataColumn[];
|
||||
|
||||
selectedRow: DataRow;
|
||||
rowsChanged: Subject<Array<DataRow>>;
|
||||
|
||||
static generateSchema(data: any[]) {
|
||||
let schema = [];
|
||||
@@ -78,6 +77,8 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
this.sort(sortable[0].key, 'asc');
|
||||
}
|
||||
}
|
||||
|
||||
this.rowsChanged = new Subject<Array<DataRow>>();
|
||||
}
|
||||
|
||||
getRows(): Array<DataRow> {
|
||||
@@ -87,6 +88,7 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
setRows(rows: Array<DataRow>) {
|
||||
this._rows = rows || [];
|
||||
this.sort();
|
||||
this.rowsChanged.next(this._rows);
|
||||
}
|
||||
|
||||
getColumns(): Array<DataColumn> {
|
||||
@@ -107,14 +109,6 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
|
||||
let value = row.getValue(col.key);
|
||||
|
||||
if (col.type === 'date') {
|
||||
try {
|
||||
return this.formatDate(col, value);
|
||||
} catch (err) {
|
||||
console.error(`Error parsing date ${value} to format ${col.format}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (col.type === 'icon') {
|
||||
const icon = row.getValue(col.key);
|
||||
return icon;
|
||||
@@ -123,21 +117,6 @@ export class ObjectDataTableAdapter implements DataTableAdapter {
|
||||
return value;
|
||||
}
|
||||
|
||||
formatDate(col: DataColumn, value: any): string {
|
||||
if (col.type === 'date') {
|
||||
const format = col.format || 'medium';
|
||||
if (format === 'timeAgo') {
|
||||
const timeAgoPipe = new TimeAgoPipe();
|
||||
return timeAgoPipe.transform(value);
|
||||
} else {
|
||||
const datePipe = new DatePipe('en-US');
|
||||
return datePipe.transform(value, format);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
getSorting(): DataSorting {
|
||||
return this._sorting;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { LocationCellComponent } from './components/datatable/location-cell.comp
|
||||
import { LoadingContentTemplateDirective } from './directives/loading-template.directive';
|
||||
import { NoContentTemplateDirective } from './directives/no-content-template.directive';
|
||||
import { NoPermissionTemplateDirective } from './directives/no-permission-template.directive';
|
||||
import { EmptyCustomContentDirective } from './directives/empty-custom-content.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -60,7 +61,8 @@ import { NoPermissionTemplateDirective } from './directives/no-permission-templa
|
||||
LocationCellComponent,
|
||||
NoContentTemplateDirective,
|
||||
NoPermissionTemplateDirective,
|
||||
LoadingContentTemplateDirective
|
||||
LoadingContentTemplateDirective,
|
||||
EmptyCustomContentDirective
|
||||
],
|
||||
exports: [
|
||||
DataTableComponent,
|
||||
@@ -74,7 +76,8 @@ import { NoPermissionTemplateDirective } from './directives/no-permission-templa
|
||||
LocationCellComponent,
|
||||
NoContentTemplateDirective,
|
||||
NoPermissionTemplateDirective,
|
||||
LoadingContentTemplateDirective
|
||||
LoadingContentTemplateDirective,
|
||||
EmptyCustomContentDirective
|
||||
]
|
||||
})
|
||||
export class DataTableModule {}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* 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 { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: 'adf-empty-custom-content'
|
||||
})
|
||||
|
||||
export class EmptyCustomContentDirective {}
|
||||
@@ -15,44 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { DataTableCellComponent } from '../components/datatable/datatable-cell.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { DataTableComponent } from '../components/datatable/datatable.component';
|
||||
import { DateCellComponent } from '../components/datatable/date-cell.component';
|
||||
import { FileSizeCellComponent } from '../components/datatable/filesize-cell.component';
|
||||
import { LocationCellComponent } from '../components/datatable/location-cell.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { LoadingContentTemplateDirective } from './loading-template.directive';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
|
||||
describe('LoadingContentTemplateDirective', () => {
|
||||
|
||||
let fixture: ComponentFixture<DataTableComponent>;
|
||||
let dataTable: DataTableComponent;
|
||||
let directive: LoadingContentTemplateDirective;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
DataTableCellComponent,
|
||||
LocationCellComponent,
|
||||
LoadingContentTemplateDirective,
|
||||
DateCellComponent,
|
||||
FileSizeCellComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
let fixture = TestBed.createComponent(DataTableComponent);
|
||||
fixture = TestBed.createComponent(DataTableComponent);
|
||||
dataTable = fixture.componentInstance;
|
||||
directive = new LoadingContentTemplateDirective(dataTable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('applies template to the datatable', () => {
|
||||
const template: any = 'test template';
|
||||
directive.template = template;
|
||||
|
||||
@@ -15,44 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { DataTableCellComponent } from '../components/datatable/datatable-cell.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { DataTableComponent } from '../components/datatable/datatable.component';
|
||||
import { DateCellComponent } from '../components/datatable/date-cell.component';
|
||||
import { FileSizeCellComponent } from '../components/datatable/filesize-cell.component';
|
||||
import { LocationCellComponent } from '../components/datatable/location-cell.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { NoContentTemplateDirective } from './no-content-template.directive';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
|
||||
describe('NoContentTemplateDirective', () => {
|
||||
|
||||
let fixture: ComponentFixture<DataTableComponent>;
|
||||
let dataTable: DataTableComponent;
|
||||
let directive: NoContentTemplateDirective;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
DataTableCellComponent,
|
||||
DateCellComponent,
|
||||
NoContentTemplateDirective,
|
||||
LocationCellComponent,
|
||||
FileSizeCellComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
let fixture = TestBed.createComponent(DataTableComponent);
|
||||
fixture = TestBed.createComponent(DataTableComponent);
|
||||
dataTable = fixture.componentInstance;
|
||||
directive = new NoContentTemplateDirective(dataTable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('applies template to the datatable', () => {
|
||||
const template: any = 'test template';
|
||||
directive.template = template;
|
||||
|
||||
@@ -15,44 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { DataTableCellComponent } from '../components/datatable/datatable-cell.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { DataTableComponent } from '../components/datatable/datatable.component';
|
||||
import { DateCellComponent } from '../components/datatable/date-cell.component';
|
||||
import { FileSizeCellComponent } from '../components/datatable/filesize-cell.component';
|
||||
import { LocationCellComponent } from '../components/datatable/location-cell.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { NoPermissionTemplateDirective } from './no-permission-template.directive';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../../testing/core.testing.module';
|
||||
|
||||
describe('NoPermissionTemplateDirective', () => {
|
||||
|
||||
let fixture: ComponentFixture<DataTableComponent>;
|
||||
let dataTable: DataTableComponent;
|
||||
let directive: NoPermissionTemplateDirective;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DataTableComponent,
|
||||
DataTableCellComponent,
|
||||
DateCellComponent,
|
||||
NoPermissionTemplateDirective,
|
||||
LocationCellComponent,
|
||||
FileSizeCellComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
let fixture = TestBed.createComponent(DataTableComponent);
|
||||
fixture = TestBed.createComponent(DataTableComponent);
|
||||
dataTable = fixture.componentInstance;
|
||||
directive = new NoPermissionTemplateDirective(dataTable);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should apply template to the datatable', () => {
|
||||
const template: any = 'test template';
|
||||
directive.template = template;
|
||||
|
||||
@@ -35,9 +35,11 @@ export * from './components/datatable/date-cell.component';
|
||||
export * from './components/datatable/empty-list.component';
|
||||
export * from './components/datatable/filesize-cell.component';
|
||||
export * from './components/datatable/location-cell.component';
|
||||
export * from './data/data-table.schema';
|
||||
|
||||
export * from './directives/loading-template.directive';
|
||||
export * from './directives/no-content-template.directive';
|
||||
export * from './directives/no-permission-template.directive';
|
||||
export * from './directives/empty-custom-content.directive';
|
||||
|
||||
export * from './datatable.module';
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
*/
|
||||
|
||||
import { Component, ViewChildren } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { HighlightTransformService } from '../services/highlight-transform.service';
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreModule } from '../core.module';
|
||||
|
||||
const template: string = `
|
||||
<div id="outerDiv1" adf-highlight adf-highlight-selector=".highlightable" adf-highlight-class="highlight-for-free-willy">
|
||||
@@ -42,16 +44,14 @@ describe('HighlightDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let component: TestComponent;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TestComponent
|
||||
],
|
||||
providers: [
|
||||
HighlightTransformService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
@@ -15,64 +15,160 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Component, ContentChildren } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { AuthenticationService } from '../services';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreModule } from '../core.module';
|
||||
import { LogoutDirective } from './logout.directive';
|
||||
|
||||
describe('LogoutDirective', () => {
|
||||
|
||||
@Component({
|
||||
selector: 'adf-test-component',
|
||||
template: '<button adf-logout></button>'
|
||||
})
|
||||
class TestComponent {}
|
||||
describe('No input', () => {
|
||||
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let router: Router;
|
||||
let authService: AuthenticationService;
|
||||
@Component({
|
||||
selector: 'adf-test-component',
|
||||
template: '<button adf-logout></button>'
|
||||
})
|
||||
class TestComponent {
|
||||
@ContentChildren(LogoutDirective)
|
||||
logoutDirective: LogoutDirective;
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let router: Router;
|
||||
let authService: AuthenticationService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
router = TestBed.get(Router);
|
||||
authService = TestBed.get(AuthenticationService);
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should redirect to login on click', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/login']);
|
||||
});
|
||||
|
||||
it('should redirect to login even on logout error', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.throw('err'));
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/login']);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
router = TestBed.get(Router);
|
||||
authService = TestBed.get(AuthenticationService);
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should redirect to login on click', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
|
||||
describe('redirectUri', () => {
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
@Component({
|
||||
selector: 'adf-test-component',
|
||||
template: '<button adf-logout redirectUri="/myCustomUri"></button>'
|
||||
})
|
||||
class TestComponent {
|
||||
@ContentChildren(LogoutDirective)
|
||||
logoutDirective: LogoutDirective;
|
||||
}
|
||||
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let router: Router;
|
||||
let authService: AuthenticationService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
router = TestBed.get(Router);
|
||||
authService = TestBed.get(AuthenticationService);
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should redirect to the the input redirectUri on click if present', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/myCustomUri']);
|
||||
});
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]);
|
||||
});
|
||||
|
||||
it('should redirect to login even on logout error', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.throw('err'));
|
||||
describe('redirectUri', () => {
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
@Component({
|
||||
selector: 'adf-test-component',
|
||||
template: '<button adf-logout [enabelRedirect]="false"></button>'
|
||||
})
|
||||
class TestComponent {
|
||||
@ContentChildren(LogoutDirective)
|
||||
logoutDirective: LogoutDirective;
|
||||
}
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).toHaveBeenCalledWith([ '/login' ]);
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let router: Router;
|
||||
let authService: AuthenticationService;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
router = TestBed.get(Router);
|
||||
authService = TestBed.get(AuthenticationService);
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not redirect if enabelRedirect is false', () => {
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
spyOn(authService, 'logout').and.returnValue(Observable.of(true));
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
expect(authService.logout).toHaveBeenCalled();
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Directive, ElementRef, OnInit, Renderer2 } from '@angular/core';
|
||||
import { Input, Directive, ElementRef, OnInit, Renderer2 } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthenticationService } from '../services/authentication.service';
|
||||
|
||||
@@ -24,11 +24,18 @@ import { AuthenticationService } from '../services/authentication.service';
|
||||
})
|
||||
export class LogoutDirective implements OnInit {
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
private router: Router,
|
||||
private auth: AuthenticationService) {
|
||||
/** Uri to be redirect after the logout default value login */
|
||||
@Input()
|
||||
redirectUri: string = '/login';
|
||||
|
||||
/** Enable redirect after logout */
|
||||
@Input()
|
||||
enabelRedirect: boolean = true;
|
||||
|
||||
constructor(private elementRef: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
private router: Router,
|
||||
private auth: AuthenticationService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -42,12 +49,14 @@ export class LogoutDirective implements OnInit {
|
||||
|
||||
logout() {
|
||||
this.auth.logout().subscribe(
|
||||
() => this.redirectToLogin(),
|
||||
() => this.redirectToLogin()
|
||||
() => this.redirectToUri(),
|
||||
() => this.redirectToUri()
|
||||
);
|
||||
}
|
||||
|
||||
redirectToLogin() {
|
||||
this.router.navigate(['/login']);
|
||||
redirectToUri() {
|
||||
if (this.enabelRedirect) {
|
||||
this.router.navigate([this.redirectUri]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,15 @@
|
||||
*/
|
||||
|
||||
import { Component, DebugElement, ViewChild } from '@angular/core';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { NodeDeleteDirective } from './node-delete.directive';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreModule } from '../core.module';
|
||||
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
|
||||
import { TranslationService } from '../services/translation.service';
|
||||
import { TranslationMock } from '../mock/translation.service.mock';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -31,9 +36,10 @@ class TestComponent {
|
||||
selection = [];
|
||||
|
||||
@ViewChild(NodeDeleteDirective)
|
||||
deleteDirective;
|
||||
deleteDirective: NodeDeleteDirective;
|
||||
|
||||
onDelete = jasmine.createSpy('onDelete');
|
||||
onDelete(event) {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -47,7 +53,7 @@ class TestWithPermissionsComponent {
|
||||
selection = [];
|
||||
|
||||
@ViewChild(NodeDeleteDirective)
|
||||
deleteDirective;
|
||||
deleteDirective: NodeDeleteDirective;
|
||||
|
||||
onDelete = jasmine.createSpy('onDelete');
|
||||
}
|
||||
@@ -65,14 +71,14 @@ class TestDeletePermanentComponent {
|
||||
selection = [];
|
||||
|
||||
@ViewChild(NodeDeleteDirective)
|
||||
deleteDirective;
|
||||
deleteDirective: NodeDeleteDirective;
|
||||
|
||||
permanent = true;
|
||||
|
||||
onDelete = jasmine.createSpy('onDelete');
|
||||
}
|
||||
|
||||
describe('NodeleteDirective', () => {
|
||||
describe('NodeDeleteDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let fixtureWithPermissions: ComponentFixture<TestWithPermissionsComponent>;
|
||||
let fixtureWithPermanentComponent: ComponentFixture<TestDeletePermanentComponent>;
|
||||
@@ -84,52 +90,66 @@ describe('NodeleteDirective', () => {
|
||||
let componentWithPermanentDelete: TestDeletePermanentComponent;
|
||||
let alfrescoApi: AlfrescoApiService;
|
||||
let nodeApi;
|
||||
let deleteNodeSpy: any;
|
||||
let purgeDeletedNodeSpy: any;
|
||||
let disposableDelete: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TestComponent,
|
||||
TestWithPermissionsComponent,
|
||||
TestDeletePermanentComponent
|
||||
]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixtureWithPermissions = TestBed.createComponent(TestWithPermissionsComponent);
|
||||
fixtureWithPermanentComponent = TestBed.createComponent(TestDeletePermanentComponent);
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
TestComponent,
|
||||
TestWithPermissionsComponent,
|
||||
TestDeletePermanentComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
|
||||
{ provide: TranslationService, useClass: TranslationMock }
|
||||
]
|
||||
});
|
||||
|
||||
component = fixture.componentInstance;
|
||||
componentWithPermissions = fixtureWithPermissions.componentInstance;
|
||||
componentWithPermanentDelete = fixtureWithPermanentComponent.componentInstance;
|
||||
beforeEach(() => {
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
nodeApi = alfrescoApi.nodesApi;
|
||||
deleteNodeSpy = spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
|
||||
purgeDeletedNodeSpy = spyOn(nodeApi, 'purgeDeletedNode').and.returnValue(Promise.resolve());
|
||||
|
||||
element = fixture.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
elementWithPermissions = fixtureWithPermissions.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
elementWithPermanentDelete = fixtureWithPermanentComponent.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixtureWithPermissions = TestBed.createComponent(TestWithPermissionsComponent);
|
||||
fixtureWithPermanentComponent = TestBed.createComponent(TestDeletePermanentComponent);
|
||||
|
||||
alfrescoApi = TestBed.get(AlfrescoApiService);
|
||||
nodeApi = alfrescoApi.getInstance().nodes;
|
||||
});
|
||||
}));
|
||||
component = fixture.componentInstance;
|
||||
componentWithPermissions = fixtureWithPermissions.componentInstance;
|
||||
componentWithPermanentDelete = fixtureWithPermanentComponent.componentInstance;
|
||||
|
||||
element = fixture.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
elementWithPermissions = fixtureWithPermissions.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
elementWithPermanentDelete = fixtureWithPermanentComponent.debugElement.query(By.directive(NodeDeleteDirective));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (disposableDelete) {
|
||||
disposableDelete.unsubscribe();
|
||||
}
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
describe('Delete', () => {
|
||||
|
||||
it('should do nothing if selection is empty', () => {
|
||||
spyOn(nodeApi, 'deleteNode');
|
||||
component.selection = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.nativeElement.click();
|
||||
|
||||
expect(nodeApi.deleteNode).not.toHaveBeenCalled();
|
||||
expect(deleteNodeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should process node successfully', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = <any> [{ entry: { id: '1', name: 'name1' } }];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.SINGULAR'
|
||||
);
|
||||
@@ -144,11 +164,11 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
|
||||
it('should notify failed node deletion', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.reject('error'));
|
||||
deleteNodeSpy.and.returnValue(Promise.reject('error'));
|
||||
|
||||
component.selection = [{ entry: { id: '1', name: 'name1' } }];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.ERROR_SINGULAR'
|
||||
);
|
||||
@@ -163,14 +183,12 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
|
||||
it('should notify nodes deletion', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.PLURAL'
|
||||
);
|
||||
@@ -185,14 +203,14 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
|
||||
it('should notify failed nodes deletion', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.reject('error'));
|
||||
deleteNodeSpy.and.returnValue(Promise.reject('error'));
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.ERROR_PLURAL'
|
||||
);
|
||||
@@ -207,7 +225,7 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
|
||||
it('should notify partial deletion when only one node is successful', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.callFake((id) => {
|
||||
deleteNodeSpy.and.callFake((id) => {
|
||||
if (id === '1') {
|
||||
return Promise.reject('error');
|
||||
} else {
|
||||
@@ -220,7 +238,7 @@ describe('NodeleteDirective', () => {
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.PARTIAL_SINGULAR'
|
||||
);
|
||||
@@ -235,7 +253,7 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
|
||||
it('should notify partial deletion when some nodes are successful', (done) => {
|
||||
spyOn(nodeApi, 'deleteNode').and.callFake((id) => {
|
||||
deleteNodeSpy.and.callFake((id) => {
|
||||
if (id === '1') {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
@@ -255,7 +273,7 @@ describe('NodeleteDirective', () => {
|
||||
{ entry: { id: '3', name: 'name3' } }
|
||||
];
|
||||
|
||||
component.deleteDirective.delete.subscribe((message) => {
|
||||
disposableDelete = component.deleteDirective.delete.subscribe((message) => {
|
||||
expect(message).toBe(
|
||||
'CORE.DELETE_NODE.PARTIAL_PLURAL'
|
||||
);
|
||||
@@ -269,36 +287,40 @@ describe('NodeleteDirective', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit event when delete is done', fakeAsync(() => {
|
||||
component.onDelete.calls.reset();
|
||||
spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
|
||||
|
||||
it('should emit event when delete is done', (done) => {
|
||||
component.selection = <any> [{ entry: { id: '1', name: 'name1' } }];
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
element.nativeElement.click();
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.onDelete).toHaveBeenCalled();
|
||||
}));
|
||||
disposableDelete = component.deleteDirective.delete.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable the button if no node are selected', fakeAsync(() => {
|
||||
it('should disable the button if no node are selected', (done) => {
|
||||
component.selection = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.disabled).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
expect(element.nativeElement.disabled).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should disable the button if selected node is null', fakeAsync(() => {
|
||||
it('should disable the button if selected node is null', (done) => {
|
||||
component.selection = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.nativeElement.disabled).toEqual(true);
|
||||
}));
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.disabled).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should enable the button if nodes are selected', fakeAsync(() => {
|
||||
it('should enable the button if nodes are selected', (done) => {
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } },
|
||||
@@ -307,10 +329,13 @@ describe('NodeleteDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.nativeElement.disabled).toEqual(false);
|
||||
}));
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.disabled).toEqual(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not enable the button if adf-node-permission is present', fakeAsync(() => {
|
||||
it('should not enable the button if adf-node-permission is present', (done) => {
|
||||
elementWithPermissions.nativeElement.disabled = false;
|
||||
componentWithPermissions.selection = [];
|
||||
|
||||
@@ -324,31 +349,32 @@ describe('NodeleteDirective', () => {
|
||||
|
||||
fixtureWithPermissions.detectChanges();
|
||||
|
||||
expect(elementWithPermissions.nativeElement.disabled).toEqual(false);
|
||||
}));
|
||||
fixture.whenStable().then(() => {
|
||||
expect(elementWithPermissions.nativeElement.disabled).toEqual(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permanent', () => {
|
||||
|
||||
it('should call the api with permamnet delete option if permanent directive input is true', fakeAsync(() => {
|
||||
let deleteApi = spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve());
|
||||
|
||||
it('should call the api with permanent delete option if permanent directive input is true', (done) => {
|
||||
fixtureWithPermanentComponent.detectChanges();
|
||||
|
||||
componentWithPermanentDelete.selection = [
|
||||
{ entry: { id: '1', name: 'name1' }
|
||||
{ entry: { id: '1', name: 'name1' } }
|
||||
];
|
||||
|
||||
fixtureWithPermanentComponent.detectChanges();
|
||||
|
||||
elementWithPermanentDelete.nativeElement.click();
|
||||
tick();
|
||||
|
||||
expect(deleteApi).toHaveBeenCalledWith('1', { permanent: true });
|
||||
}));
|
||||
|
||||
it('should call the traschan api if permanent directive input is true and the file is already in the trashcan ', fakeAsync(() => {
|
||||
let deleteApi = spyOn(nodeApi, 'purgeDeletedNode').and.returnValue(Promise.resolve());
|
||||
fixture.whenStable().then(() => {
|
||||
expect(deleteNodeSpy).toHaveBeenCalledWith('1', { permanent: true });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the trashcan api if permanent directive input is true and the file is already in the trashcan ', (done) => {
|
||||
fixtureWithPermanentComponent.detectChanges();
|
||||
|
||||
componentWithPermanentDelete.selection = [
|
||||
@@ -358,10 +384,12 @@ describe('NodeleteDirective', () => {
|
||||
fixtureWithPermanentComponent.detectChanges();
|
||||
|
||||
elementWithPermanentDelete.nativeElement.click();
|
||||
tick();
|
||||
|
||||
expect(deleteApi).toHaveBeenCalledWith('1');
|
||||
}));
|
||||
fixture.whenStable().then(() => {
|
||||
expect(purgeDeletedNodeSpy).toHaveBeenCalledWith('1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,8 +96,9 @@ export class NodeDeleteDirective implements OnChanges {
|
||||
Observable.forkJoin(...batch)
|
||||
.subscribe((data: ProcessedNodeData[]) => {
|
||||
const processedItems: ProcessStatus = this.processStatus(data);
|
||||
const message = this.getMessage(processedItems);
|
||||
|
||||
this.delete.emit(this.getMessage(processedItems));
|
||||
this.delete.emit(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -112,9 +113,9 @@ export class NodeDeleteDirective implements OnChanges {
|
||||
let promise;
|
||||
|
||||
if (node.entry.hasOwnProperty('archivedAt')) {
|
||||
promise = this.alfrescoApiService.getInstance().nodes.purgeDeletedNode(id);
|
||||
promise = this.alfrescoApiService.nodesApi.purgeDeletedNode(id);
|
||||
} else {
|
||||
promise = this.alfrescoApiService.getInstance().nodes.deleteNode(id, { permanent: this.permanent });
|
||||
promise = this.alfrescoApiService.nodesApi.deleteNode(id, { permanent: this.permanent });
|
||||
}
|
||||
|
||||
return Observable.fromPromise(promise)
|
||||
|
||||
@@ -15,97 +15,78 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { NodeFavoriteDirective } from './node-favorite.directive';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div [adf-node-favorite]="selection"
|
||||
(toggle)="done()">
|
||||
</div>`
|
||||
})
|
||||
class TestComponent {
|
||||
selection;
|
||||
|
||||
done = jasmine.createSpy('done');
|
||||
}
|
||||
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
|
||||
import { AppConfigService } from '../app-config/app-config.service';
|
||||
import { StorageService } from '../services/storage.service';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreTestingModule } from '../testing/core.testing.module';
|
||||
|
||||
describe('NodeFavoriteDirective', () => {
|
||||
let component: TestComponent;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let element: DebugElement;
|
||||
let directiveInstance;
|
||||
let apiService;
|
||||
let favoritesApi;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.debugElement.query(By.directive(NodeFavoriteDirective));
|
||||
directiveInstance = element.injector.get(NodeFavoriteDirective);
|
||||
let directive;
|
||||
let alfrescoApiService;
|
||||
|
||||
apiService = TestBed.get(AlfrescoApiService);
|
||||
favoritesApi = apiService.getInstance().core.favoritesApi;
|
||||
});
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
|
||||
directive = new NodeFavoriteDirective( alfrescoApiService);
|
||||
});
|
||||
|
||||
describe('selection input change event', () => {
|
||||
it('should not call markFavoritesNodes() if input list is empty', () => {
|
||||
spyOn(directiveInstance, 'markFavoritesNodes');
|
||||
spyOn(directive, 'markFavoritesNodes');
|
||||
|
||||
component.selection = [];
|
||||
const change = new SimpleChange(null, [], true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(directiveInstance.markFavoritesNodes).not.toHaveBeenCalledWith();
|
||||
expect(directive.markFavoritesNodes).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should call markFavoritesNodes() on input change', () => {
|
||||
spyOn(directiveInstance, 'markFavoritesNodes');
|
||||
spyOn(directive, 'markFavoritesNodes');
|
||||
|
||||
component.selection = [{ entry: { id: '1', name: 'name1' } }];
|
||||
let selection = [{ entry: { id: '1', name: 'name1' } }];
|
||||
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
expect(directiveInstance.markFavoritesNodes).toHaveBeenCalledWith(component.selection);
|
||||
expect(directive.markFavoritesNodes).toHaveBeenCalledWith(selection);
|
||||
|
||||
component.selection = [
|
||||
selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '1', name: 'name1' } }
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
expect(directiveInstance.markFavoritesNodes).toHaveBeenCalledWith(component.selection);
|
||||
expect(directive.markFavoritesNodes).toHaveBeenCalledWith(selection);
|
||||
});
|
||||
|
||||
it('should reset favorites if selection is empty', fakeAsync(() => {
|
||||
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
|
||||
spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
let selection = [
|
||||
{ entry: { id: '1', name: 'name1' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.hasFavorites()).toBe(true);
|
||||
expect(directive.hasFavorites()).toBe(true);
|
||||
|
||||
component.selection = [];
|
||||
fixture.detectChanges();
|
||||
change = new SimpleChange(null, [], true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.hasFavorites()).toBe(false);
|
||||
expect(directive.hasFavorites()).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -113,76 +94,78 @@ describe('NodeFavoriteDirective', () => {
|
||||
let favoritesApiSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
favoritesApiSpy = spyOn(favoritesApi, 'getFavorite');
|
||||
favoritesApiSpy = spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'getFavorite')
|
||||
.and.returnValue(Promise.resolve());
|
||||
});
|
||||
|
||||
it('should check each selected node if it is a favorite', fakeAsync(() => {
|
||||
favoritesApiSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
const selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
tick();
|
||||
expect(favoritesApiSpy.calls.count()).toBe(2);
|
||||
|
||||
}));
|
||||
|
||||
it('should not check processed node when another is unselected', fakeAsync(() => {
|
||||
favoritesApiSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
let selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
expect(directiveInstance.favorites.length).toBe(2);
|
||||
tick();
|
||||
expect(directive.favorites.length).toBe(2);
|
||||
expect(favoritesApiSpy.calls.count()).toBe(2);
|
||||
|
||||
favoritesApiSpy.calls.reset();
|
||||
|
||||
component.selection = [
|
||||
selection = [
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
expect(directiveInstance.favorites.length).toBe(1);
|
||||
tick();
|
||||
expect(directive.favorites.length).toBe(1);
|
||||
expect(favoritesApiSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should not check processed nodes when another is selected', fakeAsync(() => {
|
||||
favoritesApiSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
let selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.favorites.length).toBe(2);
|
||||
expect(directive.favorites.length).toBe(2);
|
||||
expect(favoritesApiSpy.calls.count()).toBe(2);
|
||||
|
||||
favoritesApiSpy.calls.reset();
|
||||
|
||||
component.selection = [
|
||||
selection = [
|
||||
{ entry: { id: '1', name: 'name1' } },
|
||||
{ entry: { id: '2', name: 'name2' } },
|
||||
{ entry: { id: '3', name: 'name3' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.favorites.length).toBe(3);
|
||||
expect(directive.favorites.length).toBe(3);
|
||||
expect(favoritesApiSpy.calls.count()).toBe(1);
|
||||
}));
|
||||
});
|
||||
@@ -192,8 +175,8 @@ describe('NodeFavoriteDirective', () => {
|
||||
let addFavoriteSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
removeFavoriteSpy = spyOn(favoritesApi, 'removeFavoriteSite');
|
||||
addFavoriteSpy = spyOn(favoritesApi, 'addFavorite');
|
||||
removeFavoriteSpy = spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'removeFavoriteSite');
|
||||
addFavoriteSpy = spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'addFavorite');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -201,170 +184,216 @@ describe('NodeFavoriteDirective', () => {
|
||||
addFavoriteSpy.calls.reset();
|
||||
});
|
||||
|
||||
it('should not perform action if favorites collection is empty', () => {
|
||||
component.selection = [];
|
||||
it('should not perform action if favorites collection is empty', fakeAsync(() => {
|
||||
let change = new SimpleChange(null, [], true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
directive.toggleFavorite();
|
||||
|
||||
expect(removeFavoriteSpy).not.toHaveBeenCalled();
|
||||
expect(addFavoriteSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call addFavorite() if none is a favorite', fakeAsync(() => {
|
||||
it('should call addFavorite() if none is a favorite', () => {
|
||||
addFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: false } },
|
||||
{ entry: { id: '2', name: 'name2', isFavorite: false } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
directive.toggleFavorite();
|
||||
|
||||
expect(addFavoriteSpy.calls.argsFor(0)[1].length).toBe(2);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should call addFavorite() on node that is not a favorite in selection', fakeAsync(() => {
|
||||
it('should call addFavorite() on node that is not a favorite in selection', () => {
|
||||
addFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFile: true, isFolder: false, isFavorite: false } },
|
||||
{ entry: { id: '2', name: 'name2', isFile: true, isFolder: false, isFavorite: true } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
directive.toggleFavorite();
|
||||
|
||||
const callArgs = addFavoriteSpy.calls.argsFor(0)[1];
|
||||
const callParameter = callArgs[0];
|
||||
|
||||
expect(callArgs.length).toBe(1);
|
||||
expect(callParameter.target.file.guid).toBe('1');
|
||||
}));
|
||||
});
|
||||
|
||||
it('should call removeFavoriteSite() if all are favorites', fakeAsync(() => {
|
||||
it('should call removeFavoriteSite() if all are favorites', () => {
|
||||
removeFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } },
|
||||
{ entry: { id: '2', name: 'name2', isFavorite: true } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
directive.toggleFavorite();
|
||||
|
||||
expect(removeFavoriteSpy.calls.count()).toBe(2);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should emit event when removeFavoriteSite() is done', fakeAsync(() => {
|
||||
removeFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
spyOn(directive.toggle, 'emit');
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(component.done).toHaveBeenCalled();
|
||||
expect(directive.toggle.emit).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should emit event when addFavorite() is done', fakeAsync(() => {
|
||||
addFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
spyOn(directive.toggle, 'emit');
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: false } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(component.done).toHaveBeenCalled();
|
||||
expect(directive.toggle.emit).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should emit error event when removeFavoriteSite() fails', fakeAsync(() => {
|
||||
removeFavoriteSpy.and.returnValue(Promise.reject('error'));
|
||||
spyOn(directive.error, 'emit');
|
||||
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } }
|
||||
];
|
||||
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(directive.error.emit).toHaveBeenCalledWith('error');
|
||||
}));
|
||||
|
||||
it('should emit error event when addFavorite() fails', fakeAsync(() => {
|
||||
addFavoriteSpy.and.returnValue(Promise.reject('error'));
|
||||
spyOn(directive.error, 'emit');
|
||||
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: false } }
|
||||
];
|
||||
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(directive.error.emit).toHaveBeenCalledWith('error');
|
||||
}));
|
||||
|
||||
it('should set isFavorites items to false', fakeAsync(() => {
|
||||
removeFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.hasFavorites()).toBe(false);
|
||||
expect(directive.hasFavorites()).toBe(false);
|
||||
}));
|
||||
|
||||
it('should set isFavorites items to true', fakeAsync(() => {
|
||||
addFavoriteSpy.and.returnValue(Promise.resolve());
|
||||
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: false } }
|
||||
];
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
directive.toggleFavorite();
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.hasFavorites()).toBe(true);
|
||||
expect(directive.hasFavorites()).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getFavorite()', () => {
|
||||
it('should process node as favorite', fakeAsync(() => {
|
||||
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
|
||||
|
||||
component.selection = [
|
||||
it('should not hit server when using 6.x api', fakeAsync(() => {
|
||||
spyOn(alfrescoApiService.favoritesApi, 'getFavorite').and.callThrough();
|
||||
|
||||
const selection = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } }
|
||||
];
|
||||
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directive.favorites[0].entry.isFavorite).toBe(true);
|
||||
expect(alfrescoApiService.favoritesApi.getFavorite).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should process node as favorite', fakeAsync(() => {
|
||||
spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'getFavorite').and.returnValue(Promise.resolve());
|
||||
|
||||
const selection = [
|
||||
{ entry: { id: '1', name: 'name1' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.favorites[0].entry.isFavorite).toBe(true);
|
||||
expect(directive.favorites[0].entry.isFavorite).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not process node as favorite', fakeAsync(() => {
|
||||
spyOn(favoritesApi, 'getFavorite').and.returnValue(Promise.reject(null));
|
||||
spyOn(alfrescoApiService.getInstance().core.favoritesApi, 'getFavorite').and.returnValue(Promise.reject({}));
|
||||
|
||||
component.selection = [
|
||||
const selection = [
|
||||
{ entry: { id: '1', name: 'name1' } }
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, selection, true);
|
||||
directive.ngOnChanges({'selection': change});
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.favorites[0].entry.isFavorite).toBe(false);
|
||||
expect(directive.favorites[0].entry.isFavorite).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('hasFavorites()', () => {
|
||||
it('should return false when favorites collection is empty', () => {
|
||||
directiveInstance.favorites = [];
|
||||
directive.favorites = [];
|
||||
|
||||
const hasFavorites = directiveInstance.hasFavorites();
|
||||
const hasFavorites = directive.hasFavorites();
|
||||
|
||||
expect(hasFavorites).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when some are not favorite', () => {
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } },
|
||||
{ entry: { id: '2', name: 'name2', isFavorite: false } }
|
||||
];
|
||||
|
||||
const hasFavorites = directiveInstance.hasFavorites();
|
||||
const hasFavorites = directive.hasFavorites();
|
||||
|
||||
expect(hasFavorites).toBe(false);
|
||||
});
|
||||
|
||||
it('return true when all are favorite', () => {
|
||||
directiveInstance.favorites = [
|
||||
directive.favorites = [
|
||||
{ entry: { id: '1', name: 'name1', isFavorite: true } },
|
||||
{ entry: { id: '2', name: 'name2', isFavorite: true } }
|
||||
];
|
||||
|
||||
const hasFavorites = directiveInstance.hasFavorites();
|
||||
const hasFavorites = directive.hasFavorites();
|
||||
|
||||
expect(hasFavorites).toBe(true);
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ import 'rxjs/observable/forkJoin';
|
||||
exportAs: 'adfFavorite'
|
||||
})
|
||||
export class NodeFavoriteDirective implements OnChanges {
|
||||
private favorites: any[] = [];
|
||||
favorites: any[] = [];
|
||||
|
||||
/** Array of nodes to toggle as favorites. */
|
||||
@Input('adf-node-favorite')
|
||||
@@ -38,6 +38,9 @@ export class NodeFavoriteDirective implements OnChanges {
|
||||
/** Emitted when the favorite setting is complete. */
|
||||
@Output() toggle: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/** Emitted when the favorite setting has fail. */
|
||||
@Output() error: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
this.toggleFavorite();
|
||||
@@ -68,24 +71,30 @@ export class NodeFavoriteDirective implements OnChanges {
|
||||
// shared files have nodeId
|
||||
const id = selected.entry.nodeId || selected.entry.id;
|
||||
|
||||
return Observable.fromPromise(this.alfrescoApiService.getInstance().core.favoritesApi.removeFavoriteSite('-me-', id));
|
||||
return Observable.fromPromise(this.alfrescoApiService.favoritesApi.removeFavoriteSite('-me-', id));
|
||||
});
|
||||
|
||||
Observable.forkJoin(batch).subscribe(() => {
|
||||
this.favorites.map(selected => selected.entry.isFavorite = false);
|
||||
this.toggle.emit();
|
||||
});
|
||||
Observable.forkJoin(batch).subscribe(
|
||||
() => {
|
||||
this.favorites.map(selected => selected.entry.isFavorite = false);
|
||||
this.toggle.emit();
|
||||
},
|
||||
error => this.error.emit(error)
|
||||
);
|
||||
}
|
||||
|
||||
if (!every) {
|
||||
const notFavorite = this.favorites.filter((node) => !node.entry.isFavorite);
|
||||
const body: FavoriteBody[] = notFavorite.map((node) => this.createFavoriteBody(node));
|
||||
|
||||
Observable.fromPromise(this.alfrescoApiService.getInstance().core.favoritesApi.addFavorite('-me-', <any> body))
|
||||
.subscribe(() => {
|
||||
notFavorite.map(selected => selected.entry.isFavorite = true);
|
||||
this.toggle.emit();
|
||||
});
|
||||
Observable.fromPromise(this.alfrescoApiService.favoritesApi.addFavorite('-me-', <any> body))
|
||||
.subscribe(
|
||||
() => {
|
||||
notFavorite.map(selected => selected.entry.isFavorite = true);
|
||||
this.toggle.emit();
|
||||
},
|
||||
error => this.error.emit(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +107,9 @@ export class NodeFavoriteDirective implements OnChanges {
|
||||
const result = this.diff(selection, this.favorites);
|
||||
const batch = this.getProcessBatch(result);
|
||||
|
||||
Observable.forkJoin(batch).subscribe((data) => this.favorites.push(...data));
|
||||
Observable.forkJoin(batch).subscribe((data) => {
|
||||
this.favorites.push(...data);
|
||||
});
|
||||
}
|
||||
|
||||
hasFavorites(): boolean {
|
||||
@@ -114,12 +125,19 @@ export class NodeFavoriteDirective implements OnChanges {
|
||||
}
|
||||
|
||||
private getFavorite(selected: MinimalNodeEntity): Observable<any> {
|
||||
const { name, isFile, isFolder } = selected.entry;
|
||||
// shared files have nodeId
|
||||
const id = (<any> selected).entry.nodeId || selected.entry.id;
|
||||
const node = selected.entry;
|
||||
|
||||
const promise = this.alfrescoApiService.getInstance()
|
||||
.core.favoritesApi.getFavorite('-me-', id);
|
||||
// ACS 6.x with 'isFavorite' include
|
||||
if (node && node.hasOwnProperty('isFavorite')) {
|
||||
return Observable.of(selected);
|
||||
}
|
||||
|
||||
// ACS 5.x and 6.x without 'isFavorite' include
|
||||
const { name, isFile, isFolder } = node;
|
||||
// shared files have nodeId
|
||||
const id = node.nodeId || node.id;
|
||||
|
||||
const promise = this.alfrescoApiService.favoritesApi.getFavorite('-me-', id);
|
||||
|
||||
return Observable.from(promise)
|
||||
.map(() => ({
|
||||
|
||||
@@ -16,26 +16,25 @@
|
||||
*/
|
||||
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TranslationService } from '../services';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { NotificationService } from '../services/notification.service';
|
||||
import { NodeRestoreDirective } from './node-restore.directive';
|
||||
import { setupTestBed } from '../testing/setupTestBed';
|
||||
import { CoreModule } from '../core.module';
|
||||
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div [adf-restore]="selection"
|
||||
(restore)="done()">
|
||||
(restore)="doneSpy()">
|
||||
</div>`
|
||||
})
|
||||
class TestComponent {
|
||||
selection = [];
|
||||
|
||||
done = jasmine.createSpy('done');
|
||||
doneSpy = jasmine.createSpy('doneSpy');
|
||||
}
|
||||
|
||||
describe('NodeRestoreDirective', () => {
|
||||
@@ -43,45 +42,42 @@ describe('NodeRestoreDirective', () => {
|
||||
let element: DebugElement;
|
||||
let component: TestComponent;
|
||||
let alfrescoService: AlfrescoApiService;
|
||||
let translation: TranslationService;
|
||||
let notification: NotificationService;
|
||||
let router: Router;
|
||||
let nodesService;
|
||||
let coreApi;
|
||||
let directiveInstance;
|
||||
let restoreNodeSpy: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.debugElement.query(By.directive(NodeRestoreDirective));
|
||||
directiveInstance = element.injector.get(NodeRestoreDirective);
|
||||
|
||||
alfrescoService = TestBed.get(AlfrescoApiService);
|
||||
nodesService = alfrescoService.getInstance().nodes;
|
||||
coreApi = alfrescoService.getInstance().core;
|
||||
translation = TestBed.get(TranslationService);
|
||||
notification = TestBed.get(NotificationService);
|
||||
router = TestBed.get(Router);
|
||||
});
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
NoopAnimationsModule
|
||||
],
|
||||
declarations: [
|
||||
TestComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(translation, 'instant').and.returnValue('message');
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.debugElement.query(By.directive(NodeRestoreDirective));
|
||||
directiveInstance = element.injector.get(NodeRestoreDirective);
|
||||
|
||||
alfrescoService = TestBed.get(AlfrescoApiService);
|
||||
nodesService = alfrescoService.getInstance().nodes;
|
||||
coreApi = alfrescoService.getInstance().core;
|
||||
|
||||
restoreNodeSpy = spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
|
||||
list: { entries: [] }
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should not restore when selection is empty', () => {
|
||||
spyOn(nodesService, 'restoreNode');
|
||||
|
||||
component.selection = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
@@ -90,54 +86,47 @@ describe('NodeRestoreDirective', () => {
|
||||
expect(nodesService.restoreNode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not restore nodes when selection has nodes without path', () => {
|
||||
spyOn(nodesService, 'restoreNode');
|
||||
|
||||
component.selection = [ { entry: { id: '1' } } ];
|
||||
it('should not restore nodes when selection has nodes without path', (done) => {
|
||||
component.selection = [{ entry: { id: '1' } }];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
fixture.whenStable().then(() => {
|
||||
element.triggerEventHandler('click', null);
|
||||
|
||||
expect(nodesService.restoreNode).not.toHaveBeenCalled();
|
||||
expect(nodesService.restoreNode).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call restore when selection has nodes with path', fakeAsync(() => {
|
||||
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
|
||||
list: { entries: [] }
|
||||
}));
|
||||
|
||||
it('should call restore when selection has nodes with path', (done) => {
|
||||
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(nodesService.restoreNode).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
describe('refresh()', () => {
|
||||
it('should reset selection', fakeAsync(() => {
|
||||
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
|
||||
list: { entries: [] }
|
||||
}));
|
||||
fixture.whenStable().then(() => {
|
||||
expect(nodesService.restoreNode).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should reset selection', (done) => {
|
||||
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
|
||||
|
||||
directiveInstance.restore.subscribe(() => {
|
||||
expect(directiveInstance.selection.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(directiveInstance.selection.length).toBe(1);
|
||||
fixture.whenStable().then(() => {
|
||||
expect(directiveInstance.selection.length).toBe(1);
|
||||
element.triggerEventHandler('click', null);
|
||||
});
|
||||
});
|
||||
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(directiveInstance.selection.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should reset status', fakeAsync(() => {
|
||||
it('should reset status', () => {
|
||||
directiveInstance.restoreProcessStatus.fail = [{}];
|
||||
directiveInstance.restoreProcessStatus.success = [{}];
|
||||
|
||||
@@ -145,39 +134,34 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
expect(directiveInstance.restoreProcessStatus.fail).toEqual([]);
|
||||
expect(directiveInstance.restoreProcessStatus.success).toEqual([]);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should emit event on finish', fakeAsync(() => {
|
||||
spyOn(directiveInstance, 'restoreNotification').and.callFake(() => null);
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
|
||||
list: { entries: [] }
|
||||
}));
|
||||
it('should emit event on finish', (done) => {
|
||||
spyOn(element.nativeElement, 'dispatchEvent');
|
||||
|
||||
directiveInstance.restore.subscribe(() => {
|
||||
expect(component.doneSpy).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
component.selection = [{ entry: { id: '1', path: ['somewhere-over-the-rainbow'] } }];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(component.done).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('notification', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(coreApi.nodesApi, 'getDeletedNodes').and.returnValue(Promise.resolve({
|
||||
list: { entries: [] }
|
||||
}));
|
||||
});
|
||||
|
||||
it('should notify on multiple fails', fakeAsync(() => {
|
||||
it('should notify on multiple fails', (done) => {
|
||||
const error = { message: '{ "error": {} }' };
|
||||
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.PARTIAL_PLURAL');
|
||||
done();
|
||||
});
|
||||
|
||||
spyOn(nodesService, 'restoreNode').and.callFake((id) => {
|
||||
restoreNodeSpy.and.callFake((id) => {
|
||||
if (id === '1') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -199,19 +183,18 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
});
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.PARTIAL_PLURAL',
|
||||
{ number: 2 }
|
||||
);
|
||||
}));
|
||||
|
||||
it('should notify fail when restored node exist, error 409', fakeAsync(() => {
|
||||
it('should notify fail when restored node exist, error 409', (done) => {
|
||||
const error = { message: '{ "error": { "statusCode": 409 } }' };
|
||||
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.NODE_EXISTS');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
restoreNodeSpy.and.returnValue(Promise.reject(error));
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
|
||||
@@ -219,19 +202,18 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
});
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.NODE_EXISTS',
|
||||
{ name: 'name1' }
|
||||
);
|
||||
}));
|
||||
|
||||
it('should notify fail when restored node returns different statusCode', fakeAsync(() => {
|
||||
it('should notify fail when restored node returns different statusCode', (done) => {
|
||||
const error = { message: '{ "error": { "statusCode": 404 } }' };
|
||||
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
|
||||
restoreNodeSpy.and.returnValue(Promise.reject(error));
|
||||
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.GENERIC');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
|
||||
@@ -239,19 +221,17 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
});
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.GENERIC',
|
||||
{ name: 'name1' }
|
||||
);
|
||||
}));
|
||||
|
||||
it('should notify fail when restored node location is missing', fakeAsync(() => {
|
||||
it('should notify fail when restored node location is missing', (done) => {
|
||||
const error = { message: '{ "error": { } }' };
|
||||
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.reject(error));
|
||||
restoreNodeSpy.and.returnValue(Promise.reject(error));
|
||||
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.LOCATION_MISSING');
|
||||
done();
|
||||
});
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
|
||||
@@ -259,17 +239,17 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
});
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.LOCATION_MISSING',
|
||||
{ name: 'name1' }
|
||||
);
|
||||
}));
|
||||
it('should notify success when restore multiple nodes', (done) => {
|
||||
|
||||
it('should notify success when restore multiple nodes', fakeAsync(() => {
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
spyOn(nodesService, 'restoreNode').and.callFake((id) => {
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.PLURAL');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
restoreNodeSpy.and.callFake((id) => {
|
||||
if (id === '1') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -286,16 +266,15 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.PLURAL'
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should notify success on restore selected node', fakeAsync(() => {
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.throw(null) });
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
it('should notify success on restore selected node', (done) => {
|
||||
directiveInstance.restore.subscribe((event) => {
|
||||
expect(event.message).toEqual('CORE.RESTORE_NODE.SINGULAR');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.selection = [
|
||||
{ entry: { id: '1', name: 'name1', path: ['somewhere-over-the-rainbow'] } }
|
||||
@@ -303,36 +282,8 @@ describe('NodeRestoreDirective', () => {
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(translation.instant).toHaveBeenCalledWith(
|
||||
'CORE.RESTORE_NODE.SINGULAR',
|
||||
{ name: 'name1' }
|
||||
);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should navigate to restored node location onAction', fakeAsync(() => {
|
||||
spyOn(router, 'navigate');
|
||||
spyOn(nodesService, 'restoreNode').and.returnValue(Promise.resolve());
|
||||
spyOn(notification, 'openSnackMessageAction').and.returnValue({ onAction: () => Observable.of({}) });
|
||||
|
||||
component.selection = [
|
||||
{
|
||||
entry: {
|
||||
id: '1',
|
||||
name: 'name1',
|
||||
path: {
|
||||
elements: ['somewhere-over-the-rainbow']
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
element.triggerEventHandler('click', null);
|
||||
tick();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,16 +18,20 @@
|
||||
/* tslint:disable:component-selector no-input-rename */
|
||||
|
||||
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { DeletedNodeEntry, DeletedNodesPaging, PathInfoEntity } from 'alfresco-js-api';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AlfrescoApiService } from '../services/alfresco-api.service';
|
||||
import { NotificationService } from '../services/notification.service';
|
||||
import { TranslationService } from '../services/translation.service';
|
||||
import 'rxjs/add/observable/from';
|
||||
import 'rxjs/add/observable/zip';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
|
||||
export class RestoreMessageModel {
|
||||
message: string;
|
||||
path: PathInfoEntity;
|
||||
action: string;
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[adf-restore]'
|
||||
})
|
||||
@@ -38,13 +42,13 @@ export class NodeRestoreDirective {
|
||||
@Input('adf-restore')
|
||||
selection: DeletedNodeEntry[];
|
||||
|
||||
/** Path to restored node. */
|
||||
/** @deprecated 2.4.0 Path to restored node. */
|
||||
@Input()
|
||||
location: string = '';
|
||||
|
||||
/** Emitted when restoration is complete. */
|
||||
@Output()
|
||||
restore: EventEmitter<any> = new EventEmitter();
|
||||
restore: EventEmitter<RestoreMessageModel> = new EventEmitter();
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
@@ -52,9 +56,7 @@ export class NodeRestoreDirective {
|
||||
}
|
||||
|
||||
constructor(private alfrescoApiService: AlfrescoApiService,
|
||||
private translation: TranslationService,
|
||||
private router: Router,
|
||||
private notification: NotificationService) {
|
||||
private translation: TranslationService) {
|
||||
this.restoreProcessStatus = this.processStatus();
|
||||
}
|
||||
|
||||
@@ -65,36 +67,35 @@ export class NodeRestoreDirective {
|
||||
|
||||
const nodesWithPath = this.getNodesWithPath(selection);
|
||||
|
||||
if (selection.length && !nodesWithPath.length) {
|
||||
if (selection.length && nodesWithPath.length) {
|
||||
|
||||
this.restoreNodesBatch(nodesWithPath)
|
||||
.do((restoredNodes) => {
|
||||
const status = this.processStatus(restoredNodes);
|
||||
|
||||
this.restoreProcessStatus.fail.push(...status.fail);
|
||||
this.restoreProcessStatus.success.push(...status.success);
|
||||
})
|
||||
.mergeMap(() => this.getDeletedNodes())
|
||||
.subscribe(
|
||||
(deletedNodesList: any) => {
|
||||
const { entries: nodelist } = deletedNodesList.list;
|
||||
const { fail: restoreErrorNodes } = this.restoreProcessStatus;
|
||||
const selectedNodes = this.diff(restoreErrorNodes, selection, false);
|
||||
const remainingNodes = this.diff(selectedNodes, nodelist);
|
||||
|
||||
if (!remainingNodes.length) {
|
||||
this.notification();
|
||||
} else {
|
||||
this.recover(remainingNodes);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.restoreProcessStatus.fail.push(...selection);
|
||||
this.restoreNotification();
|
||||
this.refresh();
|
||||
this.notification();
|
||||
return;
|
||||
}
|
||||
|
||||
this.restoreNodesBatch(nodesWithPath)
|
||||
.do((restoredNodes) => {
|
||||
const status = this.processStatus(restoredNodes);
|
||||
|
||||
this.restoreProcessStatus.fail.push(...status.fail);
|
||||
this.restoreProcessStatus.success.push(...status.success);
|
||||
})
|
||||
.mergeMap(() => this.getDeletedNodes())
|
||||
.subscribe(
|
||||
(deletedNodesList: any) => {
|
||||
const { entries: nodelist } = deletedNodesList.list;
|
||||
const { fail: restoreErrorNodes } = this.restoreProcessStatus;
|
||||
const selectedNodes = this.diff(restoreErrorNodes, selection, false);
|
||||
const remainingNodes = this.diff(selectedNodes, nodelist);
|
||||
|
||||
if (!remainingNodes.length) {
|
||||
this.restoreNotification();
|
||||
this.refresh();
|
||||
} else {
|
||||
this.recover(remainingNodes);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private restoreNodesBatch(batch: DeletedNodeEntry[]): Observable<DeletedNodeEntry[]> {
|
||||
@@ -133,12 +134,6 @@ export class NodeRestoreDirective {
|
||||
});
|
||||
}
|
||||
|
||||
private navigateLocation(path: PathInfoEntity) {
|
||||
const parent = path.elements[path.elements.length - 1];
|
||||
|
||||
this.router.navigate([this.location, parent.id]);
|
||||
}
|
||||
|
||||
private diff(selection, list, fromList = true): any {
|
||||
const ids = selection.map(item => item.entry.id);
|
||||
|
||||
@@ -246,21 +241,27 @@ export class NodeRestoreDirective {
|
||||
}
|
||||
}
|
||||
|
||||
private restoreNotification(): void {
|
||||
private notification(): void {
|
||||
const status = Object.assign({}, this.restoreProcessStatus);
|
||||
|
||||
let message = this.getRestoreMessage();
|
||||
this.reset();
|
||||
|
||||
const action = (status.oneSucceeded && !status.someFailed) ? this.translation.instant('CORE.RESTORE_NODE.VIEW') : '';
|
||||
|
||||
this.notification.openSnackMessageAction(message, action)
|
||||
.onAction()
|
||||
.subscribe(() => this.navigateLocation(status.success[0].entry.path));
|
||||
let path;
|
||||
if (status.success && status.success.length > 0) {
|
||||
path = status.success[0].entry.path;
|
||||
}
|
||||
this.restore.emit({
|
||||
message: message,
|
||||
action: action,
|
||||
path: path
|
||||
});
|
||||
}
|
||||
|
||||
private refresh(): void {
|
||||
private reset(): void {
|
||||
this.restoreProcessStatus.reset();
|
||||
this.selection = [];
|
||||
this.restore.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../widgets/error/error.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormRenderingService } from './../../services/form-rendering.service';
|
||||
import { WidgetVisibilityService } from './../../services/widget-visibility.service';
|
||||
import { FormFieldModel, FormFieldTypes, FormModel } from './../widgets/core/index';
|
||||
import { InputMaskDirective } from './../widgets/text/text-mask.component';
|
||||
import { TextWidgetComponent, CheckboxWidgetComponent, WidgetComponent } from '../widgets/index';
|
||||
import { TextWidgetComponent, CheckboxWidgetComponent } from '../widgets/index';
|
||||
import { FormFieldComponent } from './form-field.component';
|
||||
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
|
||||
import { FormService } from '../../services/form.service';
|
||||
import { EcmModelService } from '../../services/ecm-model.service';
|
||||
import { setupTestBed } from '../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('FormFieldComponent', () => {
|
||||
|
||||
@@ -36,35 +32,12 @@ describe('FormFieldComponent', () => {
|
||||
|
||||
let formRenderingService: FormRenderingService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
FormFieldComponent,
|
||||
WidgetComponent,
|
||||
TextWidgetComponent,
|
||||
CheckboxWidgetComponent,
|
||||
InputMaskDirective,
|
||||
ErrorWidgetComponent],
|
||||
providers: [
|
||||
FormRenderingService,
|
||||
WidgetVisibilityService,
|
||||
FormService,
|
||||
EcmModelService
|
||||
]
|
||||
});
|
||||
|
||||
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||
set: {
|
||||
entryComponents: [WidgetComponent, TextWidgetComponent, CheckboxWidgetComponent]
|
||||
}
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FormFieldComponent);
|
||||
@@ -77,7 +50,7 @@ describe('FormFieldComponent', () => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should create default component instance', () => {
|
||||
it('should create default component instance', (done) => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT,
|
||||
id: 'FAKE-TXT-WIDGET'
|
||||
@@ -86,25 +59,32 @@ describe('FormFieldComponent', () => {
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.componentType).toBe(TextWidgetComponent);
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.instance instanceof TextWidgetComponent).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create custom component instance', () => {
|
||||
it('should create custom component instance', (done) => {
|
||||
formRenderingService.setComponentTypeResolver(FormFieldTypes.AMOUNT, () => CheckboxWidgetComponent, true);
|
||||
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT,
|
||||
type: FormFieldTypes.AMOUNT,
|
||||
id: 'FAKE-TXT-WIDGET'
|
||||
});
|
||||
|
||||
formRenderingService.setComponentTypeResolver(FormFieldTypes.TEXT, () => CheckboxWidgetComponent, true);
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.componentType).toBe(CheckboxWidgetComponent);
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.componentRef).toBeDefined();
|
||||
expect(component.componentRef.instance instanceof CheckboxWidgetComponent).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should require component type to be resolved', () => {
|
||||
it('should require component type to be resolved', (done) => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT,
|
||||
id: 'FAKE-TXT-WIDGET'
|
||||
@@ -114,11 +94,14 @@ describe('FormFieldComponent', () => {
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(formRenderingService.resolveComponentType).toHaveBeenCalled();
|
||||
expect(component.componentRef).toBeUndefined();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(formRenderingService.resolveComponentType).toHaveBeenCalled();
|
||||
expect(component.componentRef).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide the field when it is not visible', () => {
|
||||
it('should hide the field when it is not visible', (done) => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT,
|
||||
id: 'FAKE-TXT-WIDGET'
|
||||
@@ -127,10 +110,13 @@ describe('FormFieldComponent', () => {
|
||||
component.field = field;
|
||||
component.field.isVisible = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeTruthy();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the field when it is visible', () => {
|
||||
it('should show the field when it is visible', (done) => {
|
||||
let field = new FormFieldModel(form, {
|
||||
type: FormFieldTypes.TEXT,
|
||||
id: 'FAKE-TXT-WIDGET'
|
||||
@@ -138,7 +124,11 @@ describe('FormFieldComponent', () => {
|
||||
|
||||
component.field = field;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeFalsy();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(fixture.nativeElement.querySelector('#field-FAKE-TXT-WIDGET-container').hidden).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide a visible element', () => {
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslationService } from '../../index';
|
||||
import { DataTableModule } from '../../datatable/datatable.module';
|
||||
import { DataColumnModule } from '../../data-column/data-column.module';
|
||||
import { TranslationService, TranslationMock } from '../../index';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { EcmModelService } from '../services/ecm-model.service';
|
||||
import { FormService } from '../services/form.service';
|
||||
import { FormListComponent } from './form-list.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('TaskAttachmentList', () => {
|
||||
|
||||
@@ -32,28 +31,15 @@ describe('TaskAttachmentList', () => {
|
||||
let service: FormService;
|
||||
let element: any;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
DataColumnModule,
|
||||
DataTableModule,
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
FormListComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
let translateService = TestBed.get(TranslationService);
|
||||
spyOn(translateService, 'addTranslationFolder').and.stub();
|
||||
spyOn(translateService, 'get').and.callFake((key) => {
|
||||
return Observable.of(key);
|
||||
});
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
{ provide: TranslationService, useClass: TranslationMock }
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<h4 *ngIf="isTitleEnabled()">
|
||||
<div *ngIf="showValidationIcon" class="adf-form-validation-button">
|
||||
<i id="adf-valid-form-icon" class="material-icons" *ngIf="form.isValid; else no_valid_form">check_circle</i>
|
||||
<ng-template #no_valid_form>
|
||||
<i id="adf-invalid-form-icon" class="material-icons invalid-color">error</i>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="showRefreshButton" class="adf-form-reload-button">
|
||||
<button mat-icon-button (click)="onRefreshClicked()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
@mixin adf-form-component-theme($theme) {
|
||||
|
||||
$config: mat-typography-config();
|
||||
$warn: map-get($theme, warn);
|
||||
$accent: map-get($theme, accent);
|
||||
|
||||
.adf {
|
||||
&-form-container {
|
||||
max-width: 100% !important;
|
||||
max-height: 100% !important;
|
||||
|
||||
|
||||
& .mat-card {
|
||||
padding: 16px 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
& .mat-card-header-text {
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
|
||||
& .mat-tab-body-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
& .mat-tab-label {
|
||||
font-size: mat-font-size($config, subheading-2);
|
||||
line-height: mat-line-height($config, headline);
|
||||
@@ -28,47 +30,58 @@
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
& .mat-ink-bar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
|
||||
& .mat-input-wrapper {
|
||||
margin: 0px 12px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-form-title {
|
||||
font-size: mat-font-size($alfresco-typography, title);
|
||||
}
|
||||
|
||||
|
||||
&-form-debug-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
&-form-debug-container .debug-toggle-text {
|
||||
padding-left: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
&-form-debug-container .debug-toggle-text:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
&-form-reload-button {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 30px;
|
||||
}
|
||||
|
||||
|
||||
&-form-validation-button {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
top: 39px;
|
||||
color: mat-color($accent);
|
||||
|
||||
& .invalid-color {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
}
|
||||
|
||||
&-form-hide-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
&-task-title {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
|
||||
&-label {
|
||||
width: 32px;
|
||||
height: 16px;
|
||||
@@ -77,18 +90,18 @@
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
&-form-mat-card-actions {
|
||||
float: right;
|
||||
padding-bottom: 25px !important;
|
||||
padding-right: 25px !important;
|
||||
|
||||
padding-right: 25px !important;
|
||||
|
||||
& .mat-button {
|
||||
height: 36px;
|
||||
border-radius: 5px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
& .mat-button-wrapper {
|
||||
width: 58px;
|
||||
height: 20px;
|
||||
@@ -98,15 +111,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
form-field {
|
||||
width: 100%;
|
||||
|
||||
|
||||
.mat-input-element {
|
||||
font-size: mat-font-size($config, body-2);
|
||||
padding-top: 8px;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ describe('FormComponent', () => {
|
||||
});
|
||||
|
||||
it('should refresh visibility when the form is loaded', () => {
|
||||
spyOn(formService, 'getFormDefinitionById').and.returnValue(Observable.of(fakeForm));
|
||||
spyOn(formService, 'getFormDefinitionById').and.returnValue(Observable.of(JSON.parse(JSON.stringify(fakeForm))));
|
||||
const formId = '123';
|
||||
|
||||
formComponent.formId = formId;
|
||||
@@ -711,7 +711,7 @@ describe('FormComponent', () => {
|
||||
|
||||
const nodeId = '<id>';
|
||||
let change = new SimpleChange(null, nodeId, false);
|
||||
formComponent.ngOnChanges({'nodeId' : change});
|
||||
formComponent.ngOnChanges({ 'nodeId': change });
|
||||
|
||||
expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId);
|
||||
expect(formComponent.loadFormFromActiviti).toHaveBeenCalled();
|
||||
@@ -816,46 +816,44 @@ describe('FormComponent', () => {
|
||||
});
|
||||
|
||||
it('should refresh form values when data is changed', () => {
|
||||
formComponent.form = new FormModel(fakeForm);
|
||||
formComponent.form = new FormModel(JSON.parse(JSON.stringify(fakeForm)));
|
||||
let formFields = formComponent.form.getFormFields();
|
||||
|
||||
let labelField = formFields.find(field => field.id === 'label');
|
||||
let radioField = formFields.find(field => field.id === 'raduio');
|
||||
let radioField = formFields.find(field => field.id === 'radio');
|
||||
expect(labelField.value).toBe('empty');
|
||||
expect(radioField.value).toBe('option_1');
|
||||
expect(radioField.value).toBeNull();
|
||||
|
||||
let formValues: any = {};
|
||||
formValues.label = {
|
||||
id: 'option_2',
|
||||
name: 'test2'
|
||||
};
|
||||
formValues.raduio = { id: 'option_2', name: 'Option 2' };
|
||||
formValues.radio = { id: 'option_2', name: 'Option 2' };
|
||||
let change = new SimpleChange(null, formValues, false);
|
||||
formComponent.data = formValues;
|
||||
formComponent.ngOnChanges({ 'data': change });
|
||||
|
||||
formFields = formComponent.form.getFormFields();
|
||||
labelField = formFields.find(field => field.id === 'label');
|
||||
radioField = formFields.find(field => field.id === 'raduio');
|
||||
radioField = formFields.find(field => field.id === 'radio');
|
||||
expect(labelField.value).toBe('option_2');
|
||||
expect(radioField.value).toBe('option_2');
|
||||
});
|
||||
|
||||
it('should refresh radio buttons value when id is given to data', () => {
|
||||
formComponent.form = new FormModel(fakeForm);
|
||||
formComponent.form = new FormModel(JSON.parse(JSON.stringify(fakeForm)));
|
||||
let formFields = formComponent.form.getFormFields();
|
||||
let radioFieldById = formFields.find(field => field.id === 'raduio');
|
||||
|
||||
expect(radioFieldById.value).toBe('option_2');
|
||||
let radioFieldById = formFields.find(field => field.id === 'radio');
|
||||
|
||||
let formValues: any = {};
|
||||
formValues.raduio = 'option_3';
|
||||
formValues.radio = 'option_3';
|
||||
let change = new SimpleChange(null, formValues, false);
|
||||
formComponent.data = formValues;
|
||||
formComponent.ngOnChanges({ 'data': change });
|
||||
|
||||
formFields = formComponent.form.getFormFields();
|
||||
radioFieldById = formFields.find(field => field.id === 'raduio');
|
||||
radioFieldById = formFields.find(field => field.id === 'radio');
|
||||
expect(radioFieldById.value).toBe('option_3');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,15 +19,17 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { FormModule } from '../../index';
|
||||
import { formDefinitionDropdownField, formDefinitionTwoTextFields } from '../../mock';
|
||||
import { formDefinitionDropdownField, formDefinitionTwoTextFields, formDefinitionRequiredField } from '../../mock';
|
||||
import { formReadonlyTwoTextFields } from '../../mock';
|
||||
import { formDefVisibilitiFieldDependsOnNextOne, formDefVisibilitiFieldDependsOnPreviousOne } from '../../mock';
|
||||
import { FormService } from './../services/form.service';
|
||||
import { FormComponent } from './form.component';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
/** Duration of the select opening animation. */
|
||||
const SELECT_OPEN_ANIMATION = 200;
|
||||
@@ -35,7 +37,7 @@ const SELECT_OPEN_ANIMATION = 200;
|
||||
/** Duration of the select closing animation and the timeout interval for the backdrop. */
|
||||
const SELECT_CLOSE_ANIMATION = 500;
|
||||
|
||||
describe('FormComponent UI and visibiltiy', () => {
|
||||
describe('FormComponent UI and visibility', () => {
|
||||
let component: FormComponent;
|
||||
let service: FormService;
|
||||
let fixture: ComponentFixture<FormComponent>;
|
||||
@@ -47,29 +49,68 @@ describe('FormComponent UI and visibiltiy', () => {
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
FormModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FormComponent);
|
||||
component = fixture.componentInstance;
|
||||
service = fixture.debugElement.injector.get(FormService);
|
||||
service = TestBed.get(FormService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should create instance of FormComponent', () => {
|
||||
expect(fixture.componentInstance instanceof FormComponent).toBe(true, 'should create FormComponent');
|
||||
});
|
||||
|
||||
describe('Validation icon', () => {
|
||||
|
||||
it('should display valid icon for valid form', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(Observable.of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(Observable.of(formDefinitionTwoTextFields));
|
||||
|
||||
let change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeDefined();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).not.toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeNull();
|
||||
});
|
||||
|
||||
it('should display invalid icon for valid form', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(Observable.of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(Observable.of(formDefinitionRequiredField));
|
||||
|
||||
let change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeDefined();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT display validation icon when [showValidationIcon] is false', () => {
|
||||
spyOn(service, 'getTask').and.returnValue(Observable.of({}));
|
||||
spyOn(service, 'getTaskForm').and.returnValue(Observable.of(formDefinitionTwoTextFields));
|
||||
|
||||
let change = new SimpleChange(null, 1, true);
|
||||
component.ngOnChanges({ 'taskId': change });
|
||||
component.showValidationIcon = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
|
||||
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('form definition', () => {
|
||||
|
||||
it('should display two text fields form definition', () => {
|
||||
@@ -103,16 +144,16 @@ describe('FormComponent UI and visibiltiy', () => {
|
||||
const dropdown = fixture.debugElement.queryAll(By.css('#country'));
|
||||
expect(dropdown).toBeDefined();
|
||||
expect(dropdown).not.toBeNull();
|
||||
const options = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||
const optOne = options[1];
|
||||
const optTwo = options[2];
|
||||
const optThree = options[3];
|
||||
|
||||
const optOne = fixture.debugElement.queryAll(By.css('[id="mat-option-1"]'));
|
||||
const optTwo = fixture.debugElement.queryAll(By.css('[id="mat-option-2"]'));
|
||||
const optThree = fixture.debugElement.queryAll(By.css('[id="mat-option-3"]'));
|
||||
expect(optOne.nativeElement.innerText.trim()).toEqual('united kingdom');
|
||||
expect(optTwo.nativeElement.innerText.trim()).toEqual('italy');
|
||||
expect(optThree.nativeElement.innerText.trim()).toEqual('france');
|
||||
|
||||
expect(optOne[0].nativeElement.innerText.trim()).toEqual('united kingdom');
|
||||
expect(optTwo[0].nativeElement.innerText.trim()).toEqual('italy');
|
||||
expect(optThree[0].nativeElement.innerText.trim()).toEqual('france');
|
||||
|
||||
optTwo[0].nativeElement.click();
|
||||
optTwo.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(dropdown[0].nativeElement.innerText.trim()).toEqual('italy');
|
||||
tick(SELECT_CLOSE_ANIMATION);
|
||||
|
||||
@@ -18,22 +18,17 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { EntryComponentMockModule } from '../../mock/form/entry-module.mock';
|
||||
import { startFormDateWidgetMock, startFormDropdownDefinitionMock, startFormTextDefinitionMock, startMockForm, startMockFormWithTab } from '../../mock';
|
||||
import { startFormAmountWidgetMock, startFormNumberWidgetMock, startFormRadioButtonWidgetMock } from '../../mock';
|
||||
import { EcmModelService } from './../services/ecm-model.service';
|
||||
import { FormRenderingService } from './../services/form-rendering.service';
|
||||
import { FormService } from './../services/form.service';
|
||||
import { WidgetVisibilityService } from './../services/widget-visibility.service';
|
||||
import { FormFieldComponent } from './form-field/form-field.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { StartFormComponent } from './start-form.component';
|
||||
import { ContentWidgetComponent } from './widgets/content/content.widget';
|
||||
import { MASK_DIRECTIVE } from './widgets/index';
|
||||
import { WIDGET_DIRECTIVES } from './widgets/index';
|
||||
import { FormModel, FormOutcomeModel } from './widgets/index';
|
||||
import { setupTestBed } from '../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('ActivitiStartForm', () => {
|
||||
describe('StartFormComponent', () => {
|
||||
|
||||
let formService: FormService;
|
||||
let component: StartFormComponent;
|
||||
@@ -44,34 +39,18 @@ describe('ActivitiStartForm', () => {
|
||||
const exampleId1 = 'my:process1';
|
||||
const exampleId2 = 'my:process2';
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule,
|
||||
EntryComponentMockModule
|
||||
],
|
||||
declarations: [
|
||||
StartFormComponent,
|
||||
FormFieldComponent,
|
||||
ContentWidgetComponent,
|
||||
...WIDGET_DIRECTIVES,
|
||||
...MASK_DIRECTIVE
|
||||
],
|
||||
providers: [
|
||||
EcmModelService,
|
||||
FormService,
|
||||
FormRenderingService,
|
||||
WidgetVisibilityService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
fixture = TestBed.createComponent(StartFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
formService = fixture.debugElement.injector.get(FormService);
|
||||
formService = TestBed.get(FormService);
|
||||
visibilityService = TestBed.get(WidgetVisibilityService);
|
||||
|
||||
getStartFormSpy = spyOn(formService, 'getStartFormDefinition').and.returnValue(Observable.of({
|
||||
@@ -79,6 +58,10 @@ describe('ActivitiStartForm', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should load start form on change if processDefinitionId defined', () => {
|
||||
component.processDefinitionId = exampleId1;
|
||||
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
|
||||
@@ -220,10 +203,8 @@ describe('ActivitiStartForm', () => {
|
||||
const formFields = component.form.getFormFields();
|
||||
const labelField = formFields.find(field => field.id === 'number');
|
||||
const numberWidget = fixture.debugElement.nativeElement.querySelector('number-widget');
|
||||
const numberWidgetLabel = fixture.debugElement.nativeElement.querySelector('.adf-input');
|
||||
expect(labelField.type).toBe('text');
|
||||
expect(labelField.type).toBe('integer');
|
||||
expect(numberWidget).toBeDefined();
|
||||
expect(numberWidgetLabel.innerText).toBe('number');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,36 +15,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { AmountWidgetComponent } from './amount.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('AmountWidgetComponent', () => {
|
||||
|
||||
let widget: AmountWidgetComponent;
|
||||
let fixture: ComponentFixture<AmountWidgetComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
AmountWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AmountWidgetComponent);
|
||||
@@ -52,7 +40,7 @@ describe('AmountWidgetComponent', () => {
|
||||
widget = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should setup currentcy from field', () => {
|
||||
it('should setup currency from field', () => {
|
||||
const currency = 'UAH';
|
||||
widget.field = new FormFieldModel(null, {
|
||||
currency: currency
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
|
||||
.mat-grid-tile {
|
||||
overflow: visible;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,41 +15,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { WIDGET_DIRECTIVES } from '../index';
|
||||
import { MASK_DIRECTIVE } from '../index';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldComponent } from './../../form-field/form-field.component';
|
||||
import { ContentWidgetComponent } from './../content/content.widget';
|
||||
import { ContainerColumnModel } from './../core/container-column.model';
|
||||
import { FormFieldTypes } from './../core/form-field-types';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { ContainerWidgetComponent } from './container.widget';
|
||||
import { ContainerWidgetComponentModel } from './container.widget.model';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('ContainerWidgetComponent', () => {
|
||||
|
||||
let widget: ContainerWidgetComponent;
|
||||
let fixture: ComponentFixture<ContainerWidgetComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [FormFieldComponent, ContentWidgetComponent, WIDGET_DIRECTIVES, MASK_DIRECTIVE],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContainerWidgetComponent);
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TranslationService, ContentService } from '../../../../services';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { ProcessContentService } from '../../../services/process-content.service';
|
||||
import { ContentLinkModel } from '../index';
|
||||
import { ContentWidgetComponent } from './content.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslationMock } from '../../../../mock/translation.service.mock';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
@@ -62,30 +63,19 @@ describe('ContentWidgetComponent', () => {
|
||||
return new Blob([pdfData], {type: 'application/pdf'});
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
ContentWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ContentService,
|
||||
ProcessContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
{ provide: TranslationService, useClass: TranslationMock }
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
serviceContent = TestBed.get(ContentService);
|
||||
processContentService = TestBed.get(ProcessContentService);
|
||||
|
||||
let translateService = TestBed.get(TranslationService);
|
||||
spyOn(translateService, 'addTranslationFolder').and.stub();
|
||||
spyOn(translateService, 'get').and.callFake((key) => {
|
||||
return Observable.of(key);
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from './form-field-validator';
|
||||
import { FormFieldModel } from './form-field.model';
|
||||
import { FormModel } from './form.model';
|
||||
declare let moment: any;
|
||||
|
||||
describe('FormFieldValidator', () => {
|
||||
|
||||
@@ -662,6 +663,39 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take into account that max value is in UTC and NOT fail validating value checking the time', () => {
|
||||
const maxValueFromActivitiInput = '31-3-2018 12:00 AM';
|
||||
const maxValueSavedInForm = moment(maxValueFromActivitiInput, 'DD-M-YYYY hh:mm A').utc().format();
|
||||
|
||||
const localValidValue = '2018-3-30 11:59 PM';
|
||||
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
value: localValidValue,
|
||||
maxValue: maxValueSavedInForm
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take into account that max value is in UTC and fail validating value checking the time', () => {
|
||||
const maxValueFromActivitiInput = '31-3-2018 12:00 AM';
|
||||
const maxValueSavedInForm = moment(maxValueFromActivitiInput, 'DD-M-YYYY hh:mm A').utc().format();
|
||||
|
||||
let localInvalidValue = '2018-3-31 12:01 AM';
|
||||
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
value: localInvalidValue,
|
||||
maxValue: maxValueSavedInForm
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN');
|
||||
});
|
||||
|
||||
it('should succeed validating value checking the time', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
@@ -756,6 +790,39 @@ describe('FormFieldValidator', () => {
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take into account that min value is in UTC and NOT fail validating value checking the time', () => {
|
||||
const minValueFromActivitiInput = '02-3-2018 06:00 AM';
|
||||
const minValueSavedInForm = moment(minValueFromActivitiInput, 'DD-M-YYYY hh:mm A').utc().format();
|
||||
|
||||
const localValidValue = '2018-3-02 06:01 AM';
|
||||
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
value: localValidValue,
|
||||
minValue: minValueSavedInForm
|
||||
});
|
||||
|
||||
expect(validator.validate(field)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take into account that min value is in UTC and fail validating value checking the time', () => {
|
||||
const minValueFromActivitiInput = '02-3-2018 06:00 AM';
|
||||
const minValueSavedInForm = moment(minValueFromActivitiInput, 'DD-M-YYYY hh:mm A').utc().format();
|
||||
|
||||
let localInvalidValue = '2018-3-02 05:59 AM';
|
||||
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
value: localInvalidValue,
|
||||
minValue: minValueSavedInForm
|
||||
});
|
||||
|
||||
field.validationSummary = new ErrorMessageModel();
|
||||
expect(validator.validate(field)).toBeFalsy();
|
||||
expect(field.validationSummary).not.toBeNull();
|
||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN');
|
||||
});
|
||||
|
||||
it('should succeed validating value by time', () => {
|
||||
let field = new FormFieldModel(new FormModel(), {
|
||||
type: FormFieldTypes.DATETIME,
|
||||
|
||||
@@ -203,7 +203,7 @@ export class MinDateFieldValidator implements FormFieldValidator {
|
||||
|
||||
if (fieldValueData.isBefore(min)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
||||
field.validationSummary.attributes.set('minValue', field.minValue.toLocaleString());
|
||||
field.validationSummary.attributes.set('minValue', min.format(field.dateDisplayFormat).toLocaleUpperCase());
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
@@ -243,7 +243,7 @@ export class MaxDateFieldValidator implements FormFieldValidator {
|
||||
|
||||
if (d.isAfter(max)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
||||
field.validationSummary.attributes.set('maxValue', field.maxValue.toLocaleString());
|
||||
field.validationSummary.attributes.set('maxValue', max.format(field.dateDisplayFormat).toLocaleUpperCase());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -256,6 +256,7 @@ export class MinDateTimeFieldValidator implements FormFieldValidator {
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.DATETIME
|
||||
];
|
||||
MIN_DATETIME_FORMAT = 'YYYY-MM-DD hh:mm AZ';
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
@@ -278,8 +279,6 @@ export class MinDateTimeFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
private checkDateTime(field: FormFieldModel, dateFormat: string): boolean {
|
||||
const MIN_DATE_FORMAT = 'YYYY-MM-DD hh:mm A';
|
||||
|
||||
let isValid = true;
|
||||
let fieldValueDate;
|
||||
if (typeof field.value === 'string') {
|
||||
@@ -287,11 +286,11 @@ export class MinDateTimeFieldValidator implements FormFieldValidator {
|
||||
} else {
|
||||
fieldValueDate = field.value;
|
||||
}
|
||||
let min = moment(field.minValue, MIN_DATE_FORMAT);
|
||||
let min = moment(field.minValue, this.MIN_DATETIME_FORMAT);
|
||||
|
||||
if (fieldValueDate.isBefore(min)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
||||
field.validationSummary.attributes.set('minValue', min.format('D-M-YYYY hh-mm A'));
|
||||
field.validationSummary.attributes.set('minValue', min.format(field.dateDisplayFormat).replace(':', '-'));
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
@@ -303,6 +302,7 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator {
|
||||
private supportedTypes = [
|
||||
FormFieldTypes.DATETIME
|
||||
];
|
||||
MAX_DATETIME_FORMAT = 'YYYY-MM-DD hh:mm AZ';
|
||||
|
||||
isSupported(field: FormFieldModel): boolean {
|
||||
return field &&
|
||||
@@ -325,7 +325,6 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator {
|
||||
}
|
||||
|
||||
private checkDateTime(field: FormFieldModel, dateFormat: string): boolean {
|
||||
const MAX_DATE_FORMAT = 'YYYY-MM-DD hh:mm A';
|
||||
let isValid = true;
|
||||
let fieldValueDate;
|
||||
|
||||
@@ -334,11 +333,11 @@ export class MaxDateTimeFieldValidator implements FormFieldValidator {
|
||||
} else {
|
||||
fieldValueDate = field.value;
|
||||
}
|
||||
let max = moment(field.maxValue, MAX_DATE_FORMAT);
|
||||
let max = moment(field.maxValue, this.MAX_DATETIME_FORMAT);
|
||||
|
||||
if (fieldValueDate.isAfter(max)) {
|
||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
||||
field.validationSummary.attributes.set('maxValue', max.format('D-M-YYYY hh-mm A'));
|
||||
field.validationSummary.attributes.set('maxValue', max.format(field.dateDisplayFormat).replace(':', '-'));
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
|
||||
@@ -328,7 +328,7 @@ describe('FormFieldModel', () => {
|
||||
expect(form.values['radio-1']).toEqual(field.options[1]);
|
||||
});
|
||||
|
||||
it('should update form with the first radio button value', () => {
|
||||
it('radio button value should be null when no default is set', () => {
|
||||
let form = new FormModel();
|
||||
let field = new FormFieldModel(form, {
|
||||
id: 'radio-2',
|
||||
@@ -340,7 +340,7 @@ describe('FormFieldModel', () => {
|
||||
});
|
||||
|
||||
field.value = 'missing';
|
||||
expect(form.values['radio-2']).toEqual(field.options[0]);
|
||||
expect(form.values['radio-2']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not update form with display-only field value', () => {
|
||||
|
||||
@@ -317,8 +317,6 @@ export class FormFieldModel extends FormWidgetModel {
|
||||
opt.id === value || opt.name === value || (value && (opt.id === value.id || opt.name === value.name)));
|
||||
if (entry.length > 0) {
|
||||
value = entry[0].id;
|
||||
} else {
|
||||
value = this.options[0] ? this.options[0].id : json.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import moment from 'moment-es6';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { DateTimeWidgetComponent } from './date-time.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('DateTimeWidgetComponent', () => {
|
||||
|
||||
@@ -32,22 +30,12 @@ describe('DateTimeWidgetComponent', () => {
|
||||
let fixture: ComponentFixture<DateTimeWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DateTimeWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateTimeWidgetComponent);
|
||||
@@ -136,7 +124,7 @@ describe('DateTimeWidgetComponent', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should check correctly the min value with different formats', async(() => {
|
||||
xit('should check correctly the min value with different formats', async(() => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import moment from 'moment-es6';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { DateWidgetComponent } from './date.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('DateWidgetComponent', () => {
|
||||
|
||||
@@ -32,22 +30,12 @@ describe('DateWidgetComponent', () => {
|
||||
let fixture: ComponentFixture<DateWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
DateWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
ActivitiContentService,
|
||||
EcmModelService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateWidgetComponent);
|
||||
@@ -112,7 +100,12 @@ describe('DateWidgetComponent', () => {
|
||||
|
||||
describe('template check', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should show visible date widget', async(() => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
@@ -121,15 +114,8 @@ describe('DateWidgetComponent', () => {
|
||||
readOnly: 'false'
|
||||
});
|
||||
widget.field.isVisible = true;
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should show visible date widget', async(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
@@ -138,15 +124,21 @@ describe('DateWidgetComponent', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should check correctly the min value with different formats', async(() => {
|
||||
widget.field.value = '11-30-9999';
|
||||
widget.field.dateDisplayFormat = 'MM-DD-YYYY';
|
||||
widget.field.minValue = '30-12-9999';
|
||||
widget.ngOnInit();
|
||||
widget.field.validate();
|
||||
xit('should check correctly the min value with different formats', async(() => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
value: '11-30-9999',
|
||||
type: 'date',
|
||||
readOnly: 'false',
|
||||
dateDisplayFormat : 'MM-DD-YYYY',
|
||||
minValue : '30-12-9999'
|
||||
});
|
||||
fixture.detectChanges();
|
||||
widget.field.validate();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#date-field-id')).toBeDefined();
|
||||
expect(element.querySelector('#date-field-id')).not.toBeNull();
|
||||
let dateElement: any = element.querySelector('#date-field-id');
|
||||
@@ -156,7 +148,14 @@ describe('DateWidgetComponent', () => {
|
||||
}));
|
||||
|
||||
it('should show the correct format type', async(() => {
|
||||
widget.field.value = '12-30-9999';
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
value: '12-30-9999';
|
||||
type: 'date',
|
||||
readOnly: 'false'
|
||||
});
|
||||
widget.field.isVisible = true;
|
||||
widget.field.dateDisplayFormat = 'MM-DD-YYYY';
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
@@ -170,6 +169,14 @@ describe('DateWidgetComponent', () => {
|
||||
}));
|
||||
|
||||
it('should disable date button when is readonly', async(() => {
|
||||
widget.field = new FormFieldModel(new FormModel(), {
|
||||
id: 'date-field-id',
|
||||
name: 'date-name',
|
||||
value: '9-9-9999',
|
||||
type: 'date',
|
||||
readOnly: 'false'
|
||||
});
|
||||
widget.field.isVisible = true;
|
||||
widget.field.readOnly = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -18,50 +18,49 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { EcmModelService } from '../../../services/ecm-model.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { FormFieldOption } from './../core/form-field-option';
|
||||
import { FormFieldModel } from './../core/form-field.model';
|
||||
import { FormModel } from './../core/form.model';
|
||||
import { DropdownWidgetComponent } from './dropdown.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('DropdownWidgetComponent', () => {
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let formService: FormService;
|
||||
let widget: DropdownWidgetComponent;
|
||||
let visibilityService: WidgetVisibilityService;
|
||||
let fixture: ComponentFixture<DropdownWidgetComponent>;
|
||||
let element: HTMLElement;
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let fakeOptionList: FormFieldOption[] = [
|
||||
{ id: 'opt_1', name: 'option_1' },
|
||||
{ id: 'opt_2', name: 'option_2' },
|
||||
{ id: 'opt_3', name: 'option_3' }];
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [DropdownWidgetComponent, ErrorWidgetComponent],
|
||||
providers: [FormService, EcmModelService, WidgetVisibilityService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownWidgetComponent);
|
||||
widget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
formService = TestBed.get(FormService);
|
||||
visibilityService = TestBed.get(WidgetVisibilityService);
|
||||
widget.field = new FormFieldModel(new FormModel());
|
||||
});
|
||||
fixture = TestBed.createComponent(DropdownWidgetComponent);
|
||||
widget = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
formService = TestBed.get(FormService);
|
||||
visibilityService = TestBed.get(WidgetVisibilityService);
|
||||
widget.field = new FormFieldModel(new FormModel());
|
||||
}));
|
||||
|
||||
it('should require field with restUrl', () => {
|
||||
|
||||
@@ -17,23 +17,15 @@
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LogService } from '../../../../services';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { FormService } from './../../../services/form.service';
|
||||
import { FormFieldModel, FormFieldTypes, FormModel } from './../core/index';
|
||||
import { DynamicTableColumn } from './dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './dynamic-table-row.model';
|
||||
import { DynamicTableWidgetComponent } from './dynamic-table.widget';
|
||||
import { DynamicTableModel } from './dynamic-table.widget.model';
|
||||
|
||||
import { BooleanEditorComponent } from './editors/boolean/boolean.editor';
|
||||
import { DateEditorComponent } from './editors/date/date.editor';
|
||||
import { DropdownEditorComponent } from './editors/dropdown/dropdown.editor';
|
||||
import { RowEditorComponent } from './editors/row.editor';
|
||||
import { TextEditorComponent } from './editors/text/text.editor';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
let fakeFormField = {
|
||||
id: 'fake-dynamic-table',
|
||||
@@ -85,23 +77,12 @@ describe('DynamicTableWidgetComponent', () => {
|
||||
let logService: LogService;
|
||||
let formService: FormService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [DynamicTableWidgetComponent, RowEditorComponent,
|
||||
DropdownEditorComponent, DateEditorComponent, BooleanEditorComponent,
|
||||
TextEditorComponent, ErrorWidgetComponent],
|
||||
providers: [
|
||||
FormService,
|
||||
LogService,
|
||||
ActivitiContentService,
|
||||
EcmModelService,
|
||||
WidgetVisibilityService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const field = new FormFieldModel(new FormModel());
|
||||
|
||||
@@ -15,15 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import moment from 'moment-es6';
|
||||
import { MaterialModule } from '../../../../../../material.module';
|
||||
import { FormFieldModel, FormModel } from '../../../index';
|
||||
import { DynamicTableColumn } from './../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from './../../dynamic-table.widget.model';
|
||||
|
||||
import { DateEditorComponent } from './date.editor';
|
||||
import { setupTestBed } from '../../../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('DateEditorComponent', () => {
|
||||
let component: DateEditorComponent;
|
||||
@@ -32,16 +33,12 @@ describe('DateEditorComponent', () => {
|
||||
let column: DynamicTableColumn;
|
||||
let table: DynamicTableModel;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DateEditorComponent
|
||||
],
|
||||
imports: [
|
||||
MaterialModule
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DateEditorComponent);
|
||||
|
||||
+19
-20
@@ -18,16 +18,16 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { EcmModelService } from '../../../../../services/ecm-model.service';
|
||||
import { MaterialModule } from '../../../../../../material.module';
|
||||
import { FormService } from './../../../../../services/form.service';
|
||||
import { FormFieldModel, FormModel } from './../../../core/index';
|
||||
import { DynamicTableColumnOption } from './../../dynamic-table-column-option.model';
|
||||
import { DynamicTableColumn } from './../../dynamic-table-column.model';
|
||||
import { DynamicTableRow } from './../../dynamic-table-row.model';
|
||||
import { DynamicTableModel } from './../../dynamic-table.widget.model';
|
||||
|
||||
import { DropdownEditorComponent } from './dropdown.editor';
|
||||
import { setupTestBed } from '../../../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('DropdownEditorComponent', () => {
|
||||
|
||||
@@ -38,6 +38,13 @@ describe('DropdownEditorComponent', () => {
|
||||
let column: DynamicTableColumn;
|
||||
let row: DynamicTableRow;
|
||||
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
formService = new FormService(null, null, null);
|
||||
|
||||
@@ -169,13 +176,6 @@ describe('DropdownEditorComponent', () => {
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
let dropDownEditorComponent: DropdownEditorComponent;
|
||||
let fixture: ComponentFixture<DropdownEditorComponent>;
|
||||
let element: HTMLElement;
|
||||
@@ -189,21 +189,20 @@ describe('DropdownEditorComponent', () => {
|
||||
}, {id: 'opt_3', name: 'option_3'}];
|
||||
let dynamicTable: DynamicTableModel;
|
||||
|
||||
function openSelect() {
|
||||
const dropdown = fixture.debugElement.query(By.css('[class="mat-select-trigger"]'));
|
||||
dropdown.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MaterialModule],
|
||||
declarations: [DropdownEditorComponent],
|
||||
providers: [FormService, EcmModelService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DropdownEditorComponent);
|
||||
dropDownEditorComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
fixture = TestBed.createComponent(DropdownEditorComponent);
|
||||
dropDownEditorComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('and dropdown is populated via taskId', () => {
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
class="adf-input"
|
||||
type="text"
|
||||
[id]="field.id"
|
||||
[value]="getDisplayName(field.value)"
|
||||
(keyup)="onKeyUp($event)"
|
||||
[disabled]="field.readOnly"
|
||||
[(ngModel)]="value"
|
||||
[formControl] ="searchTerm"
|
||||
placeholder="{{field.placeholder}}"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="onItemSelect($event.option.value)" [displayWith]="getDisplayName">
|
||||
<mat-option *ngFor="let user of users; let i = index" [value]="user">
|
||||
<mat-autocomplete class="adf-people-widget-list" #auto="matAutocomplete" (optionSelected)="onItemSelect($event.option.value)">
|
||||
<mat-option *ngFor="let user of users$ | async; let i = index" [value]="user">
|
||||
<div class="adf-people-widget-row" id="adf-people-widget-user-{{i}}">
|
||||
<div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div>
|
||||
<div *ngIf="user.pictureId" class="adf-people-widget-image-row">
|
||||
|
||||
@@ -9,22 +9,28 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-people-widget-list {
|
||||
margin: 5px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
&-people-widget-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-people-widget-pic {
|
||||
background: mat-color($primary);
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 100px;
|
||||
color: mat-color($foreground, text);
|
||||
text-align: center;
|
||||
font-weight: bolder;
|
||||
font-size: 18px;
|
||||
text-transform: uppercase;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-people-widget-row {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
&-people-widget-image {
|
||||
|
||||
@@ -19,15 +19,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { UserProcessModel } from '../../../../models';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivitiContentService } from '../../../services/activiti-alfresco.service';
|
||||
import { FormService } from '../../../services/form.service';
|
||||
import { MaterialModule } from '../../../../material.module';
|
||||
import { FormFieldTypes } from '../core/form-field-types';
|
||||
import { FormFieldModel } from '../core/form-field.model';
|
||||
import { FormModel } from '../core/form.model';
|
||||
import { ErrorWidgetComponent } from '../error/error.component';
|
||||
import { EcmModelService } from './../../../services/ecm-model.service';
|
||||
import { PeopleWidgetComponent } from './people.widget';
|
||||
import { setupTestBed } from '../../../../testing/setupTestBed';
|
||||
import { CoreModule } from '../../../../core.module';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('PeopleWidgetComponent', () => {
|
||||
|
||||
@@ -36,22 +35,12 @@ describe('PeopleWidgetComponent', () => {
|
||||
let element: HTMLElement;
|
||||
let formService: FormService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
PeopleWidgetComponent,
|
||||
ErrorWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
FormService,
|
||||
EcmModelService,
|
||||
ActivitiContentService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
setupTestBed({
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
CoreModule.forRoot()
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PeopleWidgetComponent);
|
||||
@@ -85,7 +74,7 @@ describe('PeopleWidgetComponent', () => {
|
||||
expect(widget.getDisplayName(model)).toBe('John');
|
||||
});
|
||||
|
||||
it('should init value from the field', () => {
|
||||
it('should init value from the field', async(() => {
|
||||
widget.field.value = new UserProcessModel({
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
@@ -101,9 +90,34 @@ describe('PeopleWidgetComponent', () => {
|
||||
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect((element.querySelector('input') as HTMLInputElement).value).toBe('John Doe');
|
||||
});
|
||||
}));
|
||||
|
||||
expect((element.querySelector('input') as HTMLInputElement).value).toBe('John Doe');
|
||||
});
|
||||
it('should show the readonly value when the form is readonly', async(() => {
|
||||
widget.field.value = new UserProcessModel({
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
});
|
||||
widget.field.readOnly = true;
|
||||
widget.field.form.readOnly = true;
|
||||
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
|
||||
widget.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect((element.querySelector('input') as HTMLInputElement).value).toBe('John Doe');
|
||||
expect((element.querySelector('input') as HTMLInputElement).disabled).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should require form field to setup values on init', () => {
|
||||
widget.field.value = null;
|
||||
@@ -123,115 +137,6 @@ describe('PeopleWidgetComponent', () => {
|
||||
expect(widget.groupId).toBe('<id>');
|
||||
});
|
||||
|
||||
it('should fetch users by search term', () => {
|
||||
let users: any = [{
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
}, {
|
||||
id: 'people-id2',
|
||||
firstName: 'John',
|
||||
lastName: 'Ping'
|
||||
}];
|
||||
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(users);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
let input = widget.input;
|
||||
input.nativeElement.value = 'John';
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(formService.getWorkflowUsers).toHaveBeenCalledWith('John', widget.groupId);
|
||||
expect(widget.users).toBe(users);
|
||||
});
|
||||
|
||||
it('should fetch users by search term and group id', () => {
|
||||
let users: any = [{
|
||||
id: 'people-id',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
}, {
|
||||
id: 'people-id2',
|
||||
firstName: 'John',
|
||||
lastName: 'Ping'
|
||||
}];
|
||||
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(users);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
let input = widget.input;
|
||||
input.nativeElement.value = 'John';
|
||||
widget.groupId = '1001';
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(formService.getWorkflowUsers).toHaveBeenCalledWith('John', widget.groupId);
|
||||
expect(widget.users).toBe(users);
|
||||
});
|
||||
|
||||
it('should fetch users and show no popup', () => {
|
||||
spyOn(formService, 'getWorkflowUsers').and.returnValue(
|
||||
Observable.create(observer => {
|
||||
observer.next(null);
|
||||
observer.complete();
|
||||
})
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
let input = widget.input;
|
||||
input.nativeElement.value = 'user1';
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(formService.getWorkflowUsers).toHaveBeenCalledWith('user1', widget.groupId);
|
||||
expect(widget.users).toEqual([]);
|
||||
});
|
||||
|
||||
it('should require search term to fetch users', () => {
|
||||
spyOn(formService, 'getWorkflowUsers').and.stub();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
let input = widget.input;
|
||||
input.nativeElement.value = null;
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(formService.getWorkflowUsers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fetch users due to constraint violation', () => {
|
||||
spyOn(formService, 'getWorkflowUsers').and.stub();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
(element.querySelector('input') as HTMLInputElement).value = '123';
|
||||
widget.minTermLength = 4;
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(formService.getWorkflowUsers).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reset users when the input field is blank string', () => {
|
||||
let fakeUser = new UserProcessModel({ id: '1', email: 'ffff@fff' });
|
||||
widget.users.push(fakeUser);
|
||||
fixture.detectChanges();
|
||||
|
||||
let keyboardEvent = new KeyboardEvent('keypress');
|
||||
(element.querySelector('input') as HTMLInputElement).value = '';
|
||||
widget.onKeyUp(keyboardEvent);
|
||||
|
||||
expect(widget.users).toEqual([]);
|
||||
});
|
||||
|
||||
describe('when template is ready', () => {
|
||||
|
||||
let fakeUserResult = [
|
||||
@@ -289,6 +194,18 @@ describe('PeopleWidgetComponent', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should hide result list if input is empty', () => {
|
||||
let peopleHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
peopleHTMLElement.focus();
|
||||
peopleHTMLElement.value = '';
|
||||
peopleHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
peopleHTMLElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('#adf-people-widget-user-0'))).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user