* [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 commit d5a7abb65b.

* [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 commit fc2227ea04.

* 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:
Eugenio Romano
2018-06-25 11:24:26 +01:00
committed by GitHub
parent c3db1c2ff8
commit f9658e2879
1054 changed files with 70758 additions and 16578 deletions
+49
View File
@@ -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>
+10
View File
@@ -0,0 +1,10 @@
.about-container {
padding: 10px;
}
.adf-table-version {
width: 60%;
border: 0;
border-spacing: 0;
text-align: center;
}
+135
View File
@@ -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;
}
}
}
}
+41
View File
@@ -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 {}
+20
View File
@@ -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';
+7
View File
@@ -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 {
@@ -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);
+47 -2
View File
@@ -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));
}
}
}
+2
View File
@@ -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';
+183 -8
View File
@@ -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" }
}
}
}
}
}
}
}
}
}
+167 -78
View File
@@ -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);
}
}
@@ -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';
+19
View File
@@ -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';
@@ -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();
}
);
@@ -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) => {
@@ -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();
});
@@ -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);
});
});
+1 -23
View File
@@ -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(() => {
+9 -20
View File
@@ -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;
}
+9 -23
View File
@@ -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', () => {
+2
View File
@@ -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
View File
@@ -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;
}
+5 -2
View File
@@ -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;
+2
View File
@@ -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';
+11 -11
View File
@@ -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);
+130 -34
View File
@@ -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();
});
});
});
+19 -10
View File
@@ -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]);
}
}
}
+107 -79
View File
@@ -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();
});
});
});
});
+4 -3
View File
@@ -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);
});
+35 -17
View File
@@ -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(() => ({
+117 -166
View File
@@ -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();
}));
});
});
+47 -46
View File
@@ -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>
+36 -23
View File
@@ -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;
}
}
}
+11 -13
View File
@@ -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);
@@ -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