[GH-1655] Card Delete : added Confirmation Dialog (#1684)
* Made confirmationDialogBox from existing dialog component * Used ConfirmationDialogBox to raise warning before deletion of card property * fixes as ci checks did not pass * fixes to pass ci tests * Flash Message now visible (changed its z-index) * Confirmation Dialog shows the property name. * fixes for eslint test failure * fixes for eslint test fail * fixes for eslint test failure * fix for eslint test failure * fixed a wrong subtext string * fixed eslint issues in scss * i18n en.json for localisation updated * `en.json;`-wrong file generated by `npm run i18n-extract ` command removed * On Property Type or Name Change raises warning * On Property Type or Name Change raises Confirmation dialog Confirmation dialog box generalized for use * The affected num of cards calculation added. * If prop value not filled change after confirmation * fixes after ci eslint failure * fixes after ci eslint failure * In cardDetailProperty test considered dialog box confirmation * Added test for confirmationDialogBox * npm run fix and fixed test failure * snapshot files updated : `npm run updatesnapshot` * ran i18n-extract script * Added memo to Confirm dialog component * reverted the addition of React.memo() as the feature breaks * added confirmation for card delete * default export of Confirmation Dialog Component * improved cardDialog test considering dialog box opening * Added memo and useCallback for cnfrm dialog component * eslint formating * eslint formatting * added confirm dialog for kanban and dialog card . * updated snapshot . cardDetailProperty test failing * updated snapshot * Merge branch 'prop-update-warning-1140' into card-delete-warning-1655 * eslint formatting * Merge branch 'prop-update-warning-1140' into card-delete-warning-1655 * removed unwanted comments * imported library for failing test * Updating card modal scss * Addressed @sbishel comments * fixed duplicate width in css * updated comment in kanbanCard * fixed failing snapshot test * updated kanbanCard unit test * npm run fix * removed useState hook for confirmDialogProps * removed useState hook from cardDialog and kanbanCard for confirmDialogProps. * npm run fix * removed duplicate declaration Co-authored-by: Prakhar <> Co-authored-by: prakharporwal <prakharporwal99@gmail.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
This commit is contained in:
parent
beee6f53e7
commit
27ce296b54
15 changed files with 1338 additions and 167 deletions
|
@ -51,9 +51,16 @@
|
||||||
"CardDetail.addCardText": "add card text",
|
"CardDetail.addCardText": "add card text",
|
||||||
"CardDetail.moveContent": "move card content",
|
"CardDetail.moveContent": "move card content",
|
||||||
"CardDetail.new-comment-placeholder": "Add a comment...",
|
"CardDetail.new-comment-placeholder": "Add a comment...",
|
||||||
"CardDetailProperty.confirm-delete": "Confirm Delete Property",
|
"CardDetailProperty.confirm-delete-heading": "Confirm Delete Property",
|
||||||
"CardDetailProperty.confirm-delete-subtext": "Are you sure you want to delete the property \"{propertyName}\"? Deleting it will delete the property from all cards in this board.",
|
"CardDetailProperty.confirm-delete-subtext": "Are you sure you want to delete the property \"{propertyName}\"? Deleting it will delete the property from all cards in this board.",
|
||||||
|
"CardDetailProperty.confirm-property-name-change-subtext": "Are you sure you want to change property \"{propertyName}\" {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and can result in data loss.",
|
||||||
|
"CardDetailProperty.confirm-property-type-change": "Confirm Property Type Change!",
|
||||||
|
"CardDetailProperty.delete-action-button": "Delete",
|
||||||
|
"CardDetailProperty.property-change-action-button": "Change Property",
|
||||||
|
"CardDetailProperty.property-changed": "Changed property successfully!",
|
||||||
"CardDetailProperty.property-deleted": "Deleted {propertyName} Successfully!",
|
"CardDetailProperty.property-deleted": "Deleted {propertyName} Successfully!",
|
||||||
|
"CardDetailProperty.property-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"",
|
||||||
|
"CardDetailProperty.property-type-change-subtext": "name to \"{newPropName}\"",
|
||||||
"CardDialog.copiedLink": "Copied!",
|
"CardDialog.copiedLink": "Copied!",
|
||||||
"CardDialog.copyLink": "Copy link",
|
"CardDialog.copyLink": "Copy link",
|
||||||
"CardDialog.editing-template": "You're editing a template.",
|
"CardDialog.editing-template": "You're editing a template.",
|
||||||
|
@ -62,6 +69,7 @@
|
||||||
"Comment.delete": "Delete",
|
"Comment.delete": "Delete",
|
||||||
"CommentsList.send": "Send",
|
"CommentsList.send": "Send",
|
||||||
"ConfirmationDialog.cancel-action": "Cancel",
|
"ConfirmationDialog.cancel-action": "Cancel",
|
||||||
|
"ConfirmationDialog.confirm-action": "Confirm",
|
||||||
"ConfirmationDialog.delete-action": "Delete",
|
"ConfirmationDialog.delete-action": "Delete",
|
||||||
"ContentBlock.Delete": "Delete",
|
"ContentBlock.Delete": "Delete",
|
||||||
"ContentBlock.DeleteAction": "delete",
|
"ContentBlock.DeleteAction": "delete",
|
||||||
|
|
|
@ -592,6 +592,415 @@ exports[`components/cardDialog return cardDialog menu content 1`] = `
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`components/cardDialog return cardDialog menu content and cancel delete confirmation do nothing 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Dialog dialog-back undefined"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="toolbar"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="Button IconButton IconButton--large"
|
||||||
|
title="Close dialog"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-close CloseIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button IconButton IconButton--large"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CardDetail content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="BlockIconSelector"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-icon size-l"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="EditableAreaWrap"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
class="EditableArea Editable title"
|
||||||
|
height="0"
|
||||||
|
placeholder="Untitled"
|
||||||
|
rows="1"
|
||||||
|
spellcheck="true"
|
||||||
|
title="title"
|
||||||
|
>
|
||||||
|
title
|
||||||
|
</textarea>
|
||||||
|
<div
|
||||||
|
class="EditableAreaContainer"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
aria-hidden="true"
|
||||||
|
class="EditableAreaReference Editable title"
|
||||||
|
dir="auto"
|
||||||
|
disabled=""
|
||||||
|
rows="1"
|
||||||
|
>
|
||||||
|
title
|
||||||
|
</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertylist CardDetailProperties"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname add-property"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
+ Add a property
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div
|
||||||
|
class="CommentsList"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="commentrow"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="comment-avatar"
|
||||||
|
src="data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 100 100\\" style=\\"fill: rgb(192, 192, 192);\\"><rect width=\\"100\\" height=\\"100\\" /></svg>"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MarkdownEditor octo-editor newcomment "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-editor-preview octo-placeholder"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="octo-editor-active Editor"
|
||||||
|
style="visibility: hidden; position: absolute; top: 0px; left: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="test-id-wrapper"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="test-id"
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="EasyMDEContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror cm-s-easymde CodeMirror-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="overflow: hidden; position: relative; width: 3px; height: 0px;"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-vscrollbar"
|
||||||
|
cm-not-content="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="min-width: 1px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-hscrollbar"
|
||||||
|
cm-not-content="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="height: 100%; min-height: 1px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-scrollbar-filler"
|
||||||
|
cm-not-content="true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-gutter-filler"
|
||||||
|
cm-not-content="true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-scroll"
|
||||||
|
style="min-height: 10px;"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-sizer"
|
||||||
|
style="margin-left: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="position: relative;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-lines"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="presentation"
|
||||||
|
style="position: relative; outline: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-measure"
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
class="CodeMirror-line-like"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
xxxxxxxxxx
|
||||||
|
</span>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-measure"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style="position: relative; z-index: 1;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-cursors"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-code"
|
||||||
|
role="presentation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="position: absolute; height: 50px; width: 1px;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-gutters"
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="editor-preview-side editor-preview"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="EasyMDEContainer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror cm-s-easymde CodeMirror-wrap"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="overflow: hidden; position: relative; width: 3px; height: 0px;"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-vscrollbar"
|
||||||
|
cm-not-content="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="min-width: 1px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-hscrollbar"
|
||||||
|
cm-not-content="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="height: 100%; min-height: 1px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-scrollbar-filler"
|
||||||
|
cm-not-content="true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-gutter-filler"
|
||||||
|
cm-not-content="true"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-scroll"
|
||||||
|
style="min-height: 10px;"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-sizer"
|
||||||
|
style="margin-left: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="position: relative;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-lines"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="presentation"
|
||||||
|
style="position: relative; outline: none;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-measure"
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
class="CodeMirror-line-like"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
xxxxxxxxxx
|
||||||
|
</span>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-measure"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style="position: relative; z-index: 1;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-cursors"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-code"
|
||||||
|
role="presentation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="position: absolute; height: 50px; width: 1px;"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="CodeMirror-gutters"
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="editor-preview-side editor-preview"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CardDetail content fullwidth content-blocks"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-content CardDetailContents"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-block"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-block-margin"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MarkdownEditor octo-editor "
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-editor-preview octo-placeholder"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="octo-editor-active Editor"
|
||||||
|
style="visibility: hidden; position: absolute; top: 0px; left: 0px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="test-id-wrapper"
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
id="test-id"
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="CardDetailContentsMenu content add-content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Add content
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`components/cardDialog should match snapshot 1`] = `
|
exports[`components/cardDialog should match snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`/components/confirmationDialogBox confirmDialog should match snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Dialog dialog-back confirmation-dialog-box"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="toolbar"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="Button IconButton IconButton--large"
|
||||||
|
title="Close dialog"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-close CloseIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box-area"
|
||||||
|
title="Confirmation Dialog Box"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-heading5"
|
||||||
|
>
|
||||||
|
test-heading
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="sub-text"
|
||||||
|
>
|
||||||
|
test-sub-text
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="action-buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button emphasis--tertiary size--medium"
|
||||||
|
title="Cancel"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Cancel
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="Button emphasis--danger size--medium"
|
||||||
|
title="test-btn-text"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
test-btn-text
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`/components/confirmationDialogBox confirmDialog with Confirm Button Text should match snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Dialog dialog-back confirmation-dialog-box"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="toolbar"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="Button IconButton IconButton--large"
|
||||||
|
title="Close dialog"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-close CloseIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="box-area"
|
||||||
|
title="Confirmation Dialog Box"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-heading5"
|
||||||
|
>
|
||||||
|
test-heading
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="sub-text"
|
||||||
|
>
|
||||||
|
test-sub-text
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="action-buttons"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button emphasis--tertiary size--medium"
|
||||||
|
title="Cancel"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Cancel
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="Button emphasis--danger size--medium"
|
||||||
|
title="test-btn-text"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
test-btn-text
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -57,6 +57,7 @@ function countEmpty(cards: readonly Card[], property: IPropertyTemplate): string
|
||||||
return String(cards.length - cardsWithValue(cards, property).length)
|
return String(cards.length - cardsWithValue(cards, property).length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return count of card which have this property value as not null \\ undefined \\ ''
|
||||||
function countNotEmpty(cards: readonly Card[], property: IPropertyTemplate): string {
|
function countNotEmpty(cards: readonly Card[], property: IPropertyTemplate): string {
|
||||||
return String(cardsWithValue(cards, property).length)
|
return String(cardsWithValue(cards, property).length)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,193 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`components/cardDetail/CardDetailProperties cancel button in TypeorNameChange dialog should do nothing 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="octo-propertylist CardDetailProperties"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Owner
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyvalue"
|
||||||
|
data-testid="select-non-editable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="Label propColorDefault "
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="Label-text"
|
||||||
|
>
|
||||||
|
Jean-Luc Picard
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
MockStatus
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="Editable octo-propertyvalue"
|
||||||
|
placeholder="Empty"
|
||||||
|
spellcheck="false"
|
||||||
|
style="width: 5px;"
|
||||||
|
title="1234"
|
||||||
|
value="1234"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname add-property"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
+ Add a property
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/cardDetail/CardDetailProperties cancel on delete dialog should do nothing 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="octo-propertylist CardDetailProperties"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Owner
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyvalue"
|
||||||
|
data-testid="select-non-editable"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="Label propColorDefault "
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="Label-text"
|
||||||
|
>
|
||||||
|
Jean-Luc Picard
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
MockStatus
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="Editable octo-propertyvalue"
|
||||||
|
placeholder="Empty"
|
||||||
|
spellcheck="false"
|
||||||
|
style="width: 5px;"
|
||||||
|
title="1234"
|
||||||
|
value="1234"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname add-property"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
+ Add a property
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] = `
|
exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -42,6 +230,36 @@ exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] =
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
MockStatus
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="Editable octo-propertyvalue"
|
||||||
|
placeholder="Empty"
|
||||||
|
spellcheck="false"
|
||||||
|
style="width: 5px;"
|
||||||
|
title="1234"
|
||||||
|
value="1234"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="octo-propertyname add-property"
|
class="octo-propertyname add-property"
|
||||||
>
|
>
|
||||||
|
@ -106,6 +324,36 @@ exports[`components/cardDetail/CardDetailProperties should show property types m
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="octo-propertyrow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="menuwrapper"
|
||||||
|
class="MenuWrapper"
|
||||||
|
role="button"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="octo-propertyname"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
MockStatus
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
class="Editable octo-propertyvalue"
|
||||||
|
placeholder="Empty"
|
||||||
|
spellcheck="false"
|
||||||
|
style="width: 5px;"
|
||||||
|
title="1234"
|
||||||
|
value="1234"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="octo-propertyname add-property"
|
class="octo-propertyname add-property"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
import {render, screen, act} from '@testing-library/react'
|
import {render, screen, act} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import '@testing-library/jest-dom'
|
|
||||||
import {mocked} from 'ts-jest/utils'
|
import {mocked} from 'ts-jest/utils'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
import {createIntl} from 'react-intl'
|
import {createIntl} from 'react-intl'
|
||||||
|
|
||||||
|
import {PropertyType} from '../../blocks/board'
|
||||||
import {wrapIntl} from '../../testUtils'
|
import {wrapIntl} from '../../testUtils'
|
||||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||||
import mutator from '../../mutator'
|
import mutator from '../../mutator'
|
||||||
import {propertyTypesList, typeDisplayName} from '../../widgets/propertyMenu'
|
import {propertyTypesList, typeDisplayName} from '../../widgets/propertyMenu'
|
||||||
|
|
||||||
import {PropertyType} from '../../blocks/board'
|
|
||||||
|
|
||||||
import CardDetailProperties from './cardDetailProperties'
|
import CardDetailProperties from './cardDetailProperties'
|
||||||
|
|
||||||
jest.mock('../../mutator')
|
jest.mock('../../mutator')
|
||||||
|
@ -46,6 +44,12 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'property_id_2',
|
||||||
|
name: 'MockStatus',
|
||||||
|
type: 'number',
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const view = TestBlockFactory.createBoardView(board)
|
const view = TestBlockFactory.createBoardView(board)
|
||||||
|
@ -56,53 +60,37 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
|
|
||||||
const card = TestBlockFactory.createCard(board)
|
const card = TestBlockFactory.createCard(board)
|
||||||
card.fields.properties.property_id_1 = 'property_value_id_1'
|
card.fields.properties.property_id_1 = 'property_value_id_1'
|
||||||
|
card.fields.properties.property_id_2 = '1234'
|
||||||
|
|
||||||
|
const cardTemplate = TestBlockFactory.createCard(board)
|
||||||
|
cardTemplate.fields.isTemplate = true
|
||||||
|
|
||||||
const cards = [card]
|
const cards = [card]
|
||||||
|
|
||||||
const cardDetailProps = {
|
function renderComponent() {
|
||||||
board,
|
const component = wrapIntl((
|
||||||
card,
|
<CardDetailProperties
|
||||||
cards,
|
board={board!}
|
||||||
contents: [],
|
card={card}
|
||||||
comments: [],
|
cards={[card]}
|
||||||
activeView: view,
|
contents={[]}
|
||||||
views,
|
comments={[]}
|
||||||
readonly: false,
|
activeView={view}
|
||||||
|
views={views}
|
||||||
|
readonly={false}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
return render(component)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should match snapshot', async () => {
|
it('should match snapshot', async () => {
|
||||||
const {container} = render(
|
const {container} = renderComponent()
|
||||||
wrapIntl(
|
|
||||||
<CardDetailProperties {...cardDetailProps}/>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should rename existing select property', async () => {
|
|
||||||
render(
|
|
||||||
wrapIntl(
|
|
||||||
<CardDetailProperties {...cardDetailProps}/>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
|
||||||
userEvent.click(menuElement)
|
|
||||||
|
|
||||||
const newName = 'Owner - Renamed'
|
|
||||||
const propertyNameInput = screen.getByRole('textbox')
|
|
||||||
userEvent.type(propertyNameInput, `${newName}{enter}`)
|
|
||||||
|
|
||||||
const propertyTemplate = board.fields.cardProperties[0]
|
|
||||||
expect(mockedMutator.changePropertyTypeAndName).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockedMutator.changePropertyTypeAndName).toHaveBeenCalledWith(board, cards, propertyTemplate, 'select', newName)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show confirmation dialog when deleting existing select property', () => {
|
it('should show confirmation dialog when deleting existing select property', () => {
|
||||||
render(
|
renderComponent()
|
||||||
wrapIntl(
|
|
||||||
<CardDetailProperties {...cardDetailProps}/>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||||
userEvent.click(menuElement)
|
userEvent.click(menuElement)
|
||||||
|
@ -116,11 +104,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
|
|
||||||
it('should show property types menu', () => {
|
it('should show property types menu', () => {
|
||||||
const intl = createIntl({locale: 'en'})
|
const intl = createIntl({locale: 'en'})
|
||||||
const {container} = render(
|
const {container} = renderComponent()
|
||||||
wrapIntl(
|
|
||||||
<CardDetailProperties {...cardDetailProps}/>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||||
userEvent.click(menuElement)
|
userEvent.click(menuElement)
|
||||||
|
@ -135,12 +119,26 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('rename select property and confirm button on dialog should rename property', async () => {
|
||||||
|
const result = renderComponent()
|
||||||
|
|
||||||
|
// rename to "Owner-Renamed"
|
||||||
|
onPropertyRenameOpenConfirmationDialog(result.container)
|
||||||
|
|
||||||
|
const propertyTemplate = board.fields.cardProperties[0]
|
||||||
|
|
||||||
|
const confirmButton = result.getByTitle('Change Property')
|
||||||
|
expect(confirmButton).toBeDefined()
|
||||||
|
|
||||||
|
userEvent.click(confirmButton!)
|
||||||
|
|
||||||
|
// should be called once on confirming renaming the property
|
||||||
|
expect(mockedMutator.changePropertyTypeAndName).toBeCalledTimes(1)
|
||||||
|
expect(mockedMutator.changePropertyTypeAndName).toHaveBeenCalledWith(board, cards, propertyTemplate, 'select', 'Owner - Renamed')
|
||||||
|
})
|
||||||
|
|
||||||
it('should add new number property', async () => {
|
it('should add new number property', async () => {
|
||||||
render(
|
renderComponent()
|
||||||
wrapIntl(
|
|
||||||
<CardDetailProperties {...cardDetailProps}/>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||||
userEvent.click(menuElement)
|
userEvent.click(menuElement)
|
||||||
|
@ -151,10 +149,85 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
const args = mockedMutator.insertPropertyTemplate.mock.calls[0]
|
const args = mockedMutator.insertPropertyTemplate.mock.calls[0]
|
||||||
const template = args[3]
|
const template = args[3]
|
||||||
expect(template).toBeTruthy()
|
expect(template).toBeTruthy()
|
||||||
expect(template!.name).toMatch(/number/i)
|
expect(template!.name).toMatch(/number/i)
|
||||||
expect(template!.type).toBe('number')
|
expect(template!.type).toBe('number')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('cancel button in TypeorNameChange dialog should do nothing', () => {
|
||||||
|
const result = renderComponent()
|
||||||
|
const container = result.container
|
||||||
|
onPropertyRenameOpenConfirmationDialog(container)
|
||||||
|
|
||||||
|
const cancelButton = result.getByTitle('Cancel')
|
||||||
|
expect(cancelButton).toBeDefined()
|
||||||
|
|
||||||
|
userEvent.click(cancelButton!)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirmation on delete dialog should delete the property', () => {
|
||||||
|
const result = renderComponent()
|
||||||
|
const container = result.container
|
||||||
|
|
||||||
|
openDeleteConfirmationDialog(container)
|
||||||
|
|
||||||
|
const propertyTemplate = board.fields.cardProperties[0]
|
||||||
|
|
||||||
|
const confirmButton = result.getByTitle('Delete')
|
||||||
|
expect(confirmButton).toBeDefined()
|
||||||
|
|
||||||
|
//click delete button
|
||||||
|
userEvent.click(confirmButton!)
|
||||||
|
|
||||||
|
// should be called once on confirming delete
|
||||||
|
expect(mockedMutator.deleteProperty).toBeCalledTimes(1)
|
||||||
|
expect(mockedMutator.deleteProperty).toBeCalledWith(board, views, cards, propertyTemplate.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cancel on delete dialog should do nothing', () => {
|
||||||
|
const result = renderComponent()
|
||||||
|
const container = result.container
|
||||||
|
|
||||||
|
openDeleteConfirmationDialog(container)
|
||||||
|
|
||||||
|
const cancelButton = result.getByTitle('Cancel')
|
||||||
|
expect(cancelButton).toBeDefined()
|
||||||
|
|
||||||
|
userEvent.click(cancelButton!)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
function openDeleteConfirmationDialog(container:HTMLElement) {
|
||||||
|
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||||
|
expect(propertyLabel).toBeDefined()
|
||||||
|
userEvent.click(propertyLabel!)
|
||||||
|
|
||||||
|
const deleteOption = container.querySelector('.MenuOption.TextOption')
|
||||||
|
expect(propertyLabel).toBeDefined()
|
||||||
|
userEvent.click(deleteOption!)
|
||||||
|
|
||||||
|
const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')
|
||||||
|
expect(confirmDialog).toBeDefined()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPropertyRenameOpenConfirmationDialog(container:HTMLElement) {
|
||||||
|
const propertyLabel = container.querySelector('.MenuWrapper')
|
||||||
|
expect(propertyLabel).toBeDefined()
|
||||||
|
userEvent.click(propertyLabel!)
|
||||||
|
|
||||||
|
// write new name in the name text box
|
||||||
|
const propertyNameInput = container.querySelector('.PropertyMenu.menu-textbox')
|
||||||
|
expect(propertyNameInput).toBeDefined()
|
||||||
|
userEvent.type(propertyNameInput!, 'Owner - Renamed{enter}')
|
||||||
|
userEvent.click(propertyLabel!)
|
||||||
|
|
||||||
|
const confirmDialog = container.querySelector('.dialog.confirmation-dialog-box')
|
||||||
|
expect(confirmDialog).toBeDefined()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,15 @@ import {Card} from '../../blocks/card'
|
||||||
import {BoardView} from '../../blocks/boardView'
|
import {BoardView} from '../../blocks/boardView'
|
||||||
import {ContentBlock} from '../../blocks/contentBlock'
|
import {ContentBlock} from '../../blocks/contentBlock'
|
||||||
import {CommentBlock} from '../../blocks/commentBlock'
|
import {CommentBlock} from '../../blocks/commentBlock'
|
||||||
|
|
||||||
import mutator from '../../mutator'
|
import mutator from '../../mutator'
|
||||||
import Button from '../../widgets/buttons/button'
|
import Button from '../../widgets/buttons/button'
|
||||||
import MenuWrapper from '../../widgets/menuWrapper'
|
import MenuWrapper from '../../widgets/menuWrapper'
|
||||||
import PropertyMenu, {PropertyTypes, typeDisplayName} from '../../widgets/propertyMenu'
|
import PropertyMenu, {PropertyTypes, typeDisplayName} from '../../widgets/propertyMenu'
|
||||||
|
|
||||||
|
import Calculations from '../calculations/calculations'
|
||||||
import PropertyValueElement from '../propertyValueElement'
|
import PropertyValueElement from '../propertyValueElement'
|
||||||
import {ConfirmationDialogBox} from '../confirmationDialogBox'
|
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||||
import {sendFlashMessage} from '../flashMessages'
|
import {sendFlashMessage} from '../flashMessages'
|
||||||
import Menu from '../../widgets/menu'
|
import Menu from '../../widgets/menu'
|
||||||
import {IDType, Utils} from '../../utils'
|
import {IDType, Utils} from '../../utils'
|
||||||
|
@ -42,9 +44,93 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||||
}
|
}
|
||||||
}, [newTemplateId, board.fields.cardProperties])
|
}, [newTemplateId, board.fields.cardProperties])
|
||||||
|
|
||||||
|
const [confirmationDialogBox, setConfirmationDialogBox] = useState<ConfirmationDialogBoxProps>({heading: '', onConfirm: () => {}, onClose: () => {}})
|
||||||
const [showConfirmationDialog, setShowConfirmationDialog] = useState<boolean>(false)
|
const [showConfirmationDialog, setShowConfirmationDialog] = useState<boolean>(false)
|
||||||
const [deletingPropId, setDeletingPropId] = useState<string>('')
|
|
||||||
const [deletingPropName, setDeletingPropName] = useState<string>('')
|
function onPropertyChangeSetAndOpenConfirmationDialog(newType: PropertyType, newName: string, propertyTemplate:IPropertyTemplate) {
|
||||||
|
const oldType = propertyTemplate.type
|
||||||
|
|
||||||
|
// do nothing if no change
|
||||||
|
if (oldType === newType && propertyTemplate.name === newName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const affectsNumOfCards:string = Calculations.countNotEmpty(cards, propertyTemplate, intl)
|
||||||
|
|
||||||
|
// if no card has this value set delete the property directly without warning
|
||||||
|
if (affectsNumOfCards === '0') {
|
||||||
|
mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType, newName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let subTextString = intl.formatMessage({
|
||||||
|
id: 'CardDetailProperty.property-name-change-subtext',
|
||||||
|
defaultMessage: 'type from "{oldPropType}" to "{newPropType}"',
|
||||||
|
}, {oldPropType: oldType, newPropType: newType})
|
||||||
|
|
||||||
|
if (propertyTemplate.name !== newName) {
|
||||||
|
subTextString = intl.formatMessage({
|
||||||
|
id: 'CardDetailProperty.property-type-change-subtext',
|
||||||
|
defaultMessage: 'name to "{newPropName}"',
|
||||||
|
}, {newPropName: newName})
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfirmationDialogBox({
|
||||||
|
heading: intl.formatMessage({id: 'CardDetailProperty.confirm-property-type-change', defaultMessage: 'Confirm Property Type Change!'}),
|
||||||
|
subText: intl.formatMessage({
|
||||||
|
id: 'CardDetailProperty.confirm-property-name-change-subtext',
|
||||||
|
defaultMessage: 'Are you sure you want to change property "{propertyName}" {customText}? This will affect value(s) across {numOfCards} card(s) in this board, and can result in data loss.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
propertyName: propertyTemplate.name,
|
||||||
|
customText: subTextString,
|
||||||
|
numOfCards: affectsNumOfCards,
|
||||||
|
}),
|
||||||
|
|
||||||
|
confirmButtonText: intl.formatMessage({id: 'CardDetailProperty.property-change-action-button', defaultMessage: 'Change Property'}),
|
||||||
|
onConfirm: async () => {
|
||||||
|
setShowConfirmationDialog(false)
|
||||||
|
try {
|
||||||
|
await mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType, newName)
|
||||||
|
} catch (err:any) {
|
||||||
|
Utils.logError(`Error Changing Property And Name:${propertyTemplate.name}: ${err?.toString()}`)
|
||||||
|
}
|
||||||
|
sendFlashMessage({content: intl.formatMessage({id: 'CardDetailProperty.property-changed', defaultMessage: 'Changed property successfully!'}), severity: 'high'})
|
||||||
|
},
|
||||||
|
onClose: () => setShowConfirmationDialog(false),
|
||||||
|
})
|
||||||
|
|
||||||
|
// open confirmation dialog for property type or name change
|
||||||
|
setShowConfirmationDialog(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate:IPropertyTemplate) {
|
||||||
|
// set ConfirmationDialogBox Props
|
||||||
|
setConfirmationDialogBox({
|
||||||
|
heading: intl.formatMessage({id: 'CardDetailProperty.confirm-delete-heading', defaultMessage: 'Confirm Delete Property'}),
|
||||||
|
subText: intl.formatMessage({
|
||||||
|
id: 'CardDetailProperty.confirm-delete-subtext',
|
||||||
|
defaultMessage: 'Are you sure you want to delete the property "{propertyName}"? Deleting it will delete the property from all cards in this board.',
|
||||||
|
},
|
||||||
|
{propertyName: propertyTemplate.name}),
|
||||||
|
confirmButtonText: intl.formatMessage({id: 'CardDetailProperty.delete-action-button', defaultMessage: 'Delete'}),
|
||||||
|
onConfirm: async () => {
|
||||||
|
const deletingPropName = propertyTemplate.name
|
||||||
|
setShowConfirmationDialog(false)
|
||||||
|
try {
|
||||||
|
await mutator.deleteProperty(board, views, cards, propertyTemplate.id)
|
||||||
|
sendFlashMessage({content: intl.formatMessage({id: 'CardDetailProperty.property-deleted', defaultMessage: 'Deleted {propertyName} Successfully!'}, {propertyName: deletingPropName}), severity: 'high'})
|
||||||
|
} catch (err:any) {
|
||||||
|
Utils.logError(`Error Deleting Property!: Could Not delete Property -" + ${deletingPropName} ${err?.toString()}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose: () => setShowConfirmationDialog(false),
|
||||||
|
})
|
||||||
|
|
||||||
|
// open confirmation dialog property delete
|
||||||
|
setShowConfirmationDialog(true)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='octo-propertylist CardDetailProperties'>
|
<div className='octo-propertylist CardDetailProperties'>
|
||||||
|
@ -63,13 +149,8 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||||
propertyId={propertyTemplate.id}
|
propertyId={propertyTemplate.id}
|
||||||
propertyName={propertyTemplate.name}
|
propertyName={propertyTemplate.name}
|
||||||
propertyType={propertyTemplate.type}
|
propertyType={propertyTemplate.type}
|
||||||
onTypeAndNameChanged={(newType: PropertyType, newName: string) => mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType, newName)}
|
onTypeAndNameChanged={(newType: PropertyType, newName: string) => onPropertyChangeSetAndOpenConfirmationDialog(newType, newName, propertyTemplate)}
|
||||||
onDelete={(id: string) => {
|
onDelete={() => onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate)}
|
||||||
setDeletingPropId(id)
|
|
||||||
setDeletingPropName(propertyTemplate.name)
|
|
||||||
setShowConfirmationDialog(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
}
|
}
|
||||||
|
@ -88,21 +169,7 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||||
|
|
||||||
{showConfirmationDialog && (
|
{showConfirmationDialog && (
|
||||||
<ConfirmationDialogBox
|
<ConfirmationDialogBox
|
||||||
propertyId={deletingPropId}
|
dialogBox={confirmationDialogBox}
|
||||||
onClose={() => setShowConfirmationDialog(false)}
|
|
||||||
onConfirm={() => {
|
|
||||||
mutator.deleteProperty(board, views, cards, deletingPropId)
|
|
||||||
setShowConfirmationDialog(false)
|
|
||||||
sendFlashMessage({content: intl.formatMessage({id: 'CardDetailProperty.property-deleted', defaultMessage: 'Deleted {propertyName} Successfully!'}, {propertyName: deletingPropName}), severity: 'high'})
|
|
||||||
}}
|
|
||||||
|
|
||||||
heading={intl.formatMessage({id: 'CardDetailProperty.confirm-delete', defaultMessage: 'Confirm Delete Property'})}
|
|
||||||
subText={intl.formatMessage({
|
|
||||||
id: 'CardDetailProperty.confirm-delete-subtext',
|
|
||||||
defaultMessage: 'Are you sure you want to delete the property "{propertyName}"? Deleting it will delete the property from all cards in this board.',
|
|
||||||
},
|
|
||||||
{propertyName: deletingPropName})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -159,8 +159,58 @@ describe('components/cardDialog', () => {
|
||||||
userEvent.click(buttonMenu)
|
userEvent.click(buttonMenu)
|
||||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
userEvent.click(buttonDelete)
|
userEvent.click(buttonDelete)
|
||||||
|
|
||||||
|
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||||
|
expect(confirmDialog).toBeDefined()
|
||||||
|
|
||||||
|
const confirmButton = screen.getByTitle('Delete')
|
||||||
|
expect(confirmButton).toBeDefined()
|
||||||
|
|
||||||
|
//click delete button
|
||||||
|
userEvent.click(confirmButton!)
|
||||||
|
|
||||||
|
// should be called once on confirming delete
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
expect(mockedMutator.deleteBlock).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('return cardDialog menu content and cancel delete confirmation do nothing', async () => {
|
||||||
|
let container
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(wrapDNDIntl(
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<CardDialog
|
||||||
|
board={board}
|
||||||
|
activeView={boardView}
|
||||||
|
views={[boardView]}
|
||||||
|
cards={[card]}
|
||||||
|
cardId={card.id}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
showCard={jest.fn()}
|
||||||
|
readonly={false}
|
||||||
|
/>
|
||||||
|
</ReduxProvider>,
|
||||||
|
))
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
|
||||||
|
const buttonMenu = screen.getAllByRole('button', {name: 'menuwrapper'})[0]
|
||||||
|
userEvent.click(buttonMenu)
|
||||||
|
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||||
|
userEvent.click(buttonDelete)
|
||||||
|
|
||||||
|
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||||
|
expect(confirmDialog).toBeDefined()
|
||||||
|
|
||||||
|
const cancelButton = screen.getByTitle('Cancel')
|
||||||
|
expect(cancelButton).toBeDefined()
|
||||||
|
|
||||||
|
//click delete button
|
||||||
|
userEvent.click(cancelButton!)
|
||||||
|
|
||||||
|
// should do nothing on cancel delete dialog
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('return cardDialog menu content and do a New template from card', async () => {
|
test('return cardDialog menu content and do a New template from card', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(wrapDNDIntl(
|
render(wrapDNDIntl(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react'
|
import React, {useState} from 'react'
|
||||||
import {FormattedMessage, useIntl} from 'react-intl'
|
import {FormattedMessage, useIntl} from 'react-intl'
|
||||||
|
|
||||||
import {Board} from '../blocks/board'
|
import {Board} from '../blocks/board'
|
||||||
|
@ -17,6 +17,8 @@ import DeleteIcon from '../widgets/icons/delete'
|
||||||
import LinkIcon from '../widgets/icons/Link'
|
import LinkIcon from '../widgets/icons/Link'
|
||||||
import Menu from '../widgets/menu'
|
import Menu from '../widgets/menu'
|
||||||
|
|
||||||
|
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
|
||||||
|
|
||||||
import CardDetail from './cardDetail/cardDetail'
|
import CardDetail from './cardDetail/cardDetail'
|
||||||
import Dialog from './dialog'
|
import Dialog from './dialog'
|
||||||
import {sendFlashMessage} from './flashMessages'
|
import {sendFlashMessage} from './flashMessages'
|
||||||
|
@ -39,6 +41,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
const comments = useAppSelector(getCardComments(props.cardId))
|
const comments = useAppSelector(getCardComments(props.cardId))
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)
|
||||||
const makeTemplateClicked = async () => {
|
const makeTemplateClicked = async () => {
|
||||||
if (!card) {
|
if (!card) {
|
||||||
Utils.assertFailure('card')
|
Utils.assertFailure('card')
|
||||||
|
@ -59,6 +62,36 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const handleDeleteCard = async () => {
|
||||||
|
if (!card) {
|
||||||
|
Utils.assertFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.board.id, view: props.activeView.id, card: card.id})
|
||||||
|
await mutator.deleteBlock(card, 'delete card')
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmDialogProps: ConfirmationDialogBoxProps = {
|
||||||
|
heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),
|
||||||
|
confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),
|
||||||
|
onConfirm: handleDeleteCard,
|
||||||
|
onClose: () => {
|
||||||
|
setShowConfirmationDialogBox(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteButtonOnClick = () => {
|
||||||
|
// use may be renaming a card title
|
||||||
|
// and accidently delete the card
|
||||||
|
// so adding des
|
||||||
|
if (card?.title === '' && card?.fields.contentOrder.length === 0) {
|
||||||
|
handleDeleteCard()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowConfirmationDialogBox(true)
|
||||||
|
}
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu position='left'>
|
<Menu position='left'>
|
||||||
|
@ -66,15 +99,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
id='delete'
|
id='delete'
|
||||||
icon={<DeleteIcon/>}
|
icon={<DeleteIcon/>}
|
||||||
name='Delete'
|
name='Delete'
|
||||||
onClick={async () => {
|
onClick={handleDeleteButtonOnClick}
|
||||||
if (!card) {
|
|
||||||
Utils.assertFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: props.board.id, view: props.activeView.id, card: props.cardId})
|
|
||||||
await mutator.deleteBlock(card, 'delete card')
|
|
||||||
props.onClose()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
icon={<LinkIcon/>}
|
icon={<LinkIcon/>}
|
||||||
|
@ -101,11 +126,12 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<>
|
||||||
onClose={props.onClose}
|
<Dialog
|
||||||
toolsMenu={!props.readonly && menu}
|
onClose={props.onClose}
|
||||||
>
|
toolsMenu={!props.readonly && menu}
|
||||||
{card && card.fields.isTemplate &&
|
>
|
||||||
|
{card && card.fields.isTemplate &&
|
||||||
<div className='banner'>
|
<div className='banner'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='CardDialog.editing-template'
|
id='CardDialog.editing-template'
|
||||||
|
@ -113,7 +139,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
{card &&
|
{card &&
|
||||||
<CardDetail
|
<CardDetail
|
||||||
board={board}
|
board={board}
|
||||||
activeView={activeView}
|
activeView={activeView}
|
||||||
|
@ -125,14 +151,17 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||||
readonly={props.readonly}
|
readonly={props.readonly}
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{!card &&
|
{!card &&
|
||||||
<div className='banner error'>
|
<div className='banner error'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='CardDialog.nocard'
|
id='CardDialog.nocard'
|
||||||
defaultMessage="This card doesn't exist or is inaccessible."
|
defaultMessage="This card doesn't exist or is inaccessible."
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
.confirmation-dialog-box {
|
.confirmation-dialog-box {
|
||||||
.dialog {
|
.dialog {
|
||||||
|
max-width: 512px;
|
||||||
|
width: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 30%;
|
top: 30%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: max-content;
|
|
||||||
height: max-content;
|
height: max-content;
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
|
|
||||||
|
@ -25,36 +26,27 @@
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-area {
|
.box-area {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
padding: 48px 40px;
|
||||||
|
|
||||||
.heading {
|
|
||||||
margin-top: 2rem;
|
.text-heading5 {
|
||||||
padding: 2px 4px;
|
margin: 0 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-text {
|
.sub-text {
|
||||||
width: 26rem;
|
text-align: center;
|
||||||
word-wrap: normal;
|
|
||||||
margin: 0.5rem 3rem;
|
|
||||||
padding: 2px;
|
|
||||||
|
|
||||||
@media screen and (max-width: 400px) {
|
|
||||||
width: 12rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: grid;
|
||||||
margin: 1rem;
|
grid-gap: 10px;
|
||||||
justify-content: space-between;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
margin-top: 32px;
|
||||||
.Button {
|
|
||||||
margin: 2px 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
93
webapp/src/components/confirmationDialogBox.test.tsx
Normal file
93
webapp/src/components/confirmationDialogBox.test.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import {act, render} from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {wrapDNDIntl} from '../testUtils'
|
||||||
|
|
||||||
|
import ConfirmationDialogBox from './confirmationDialogBox'
|
||||||
|
|
||||||
|
describe('/components/confirmationDialogBox', () => {
|
||||||
|
const dialogPropsWithCnfrmBtnText = {
|
||||||
|
heading: 'test-heading',
|
||||||
|
subText: 'test-sub-text',
|
||||||
|
confirmButtonText: 'test-btn-text',
|
||||||
|
onConfirm: jest.fn(),
|
||||||
|
onClose: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogProps = {
|
||||||
|
heading: 'test-heading',
|
||||||
|
onConfirm: jest.fn(),
|
||||||
|
onClose: jest.fn(),
|
||||||
|
}
|
||||||
|
|
||||||
|
it('confirmDialog should match snapshot', async () => {
|
||||||
|
let container
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ConfirmationDialogBox
|
||||||
|
dialogBox={dialogPropsWithCnfrmBtnText}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
container = result.container
|
||||||
|
})
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirmDialog with Confirm Button Text should match snapshot', async () => {
|
||||||
|
let containerWithCnfrmBtnText
|
||||||
|
await act(async () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ConfirmationDialogBox
|
||||||
|
dialogBox={dialogPropsWithCnfrmBtnText}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
containerWithCnfrmBtnText = result.container
|
||||||
|
})
|
||||||
|
expect(containerWithCnfrmBtnText).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirm button click, run onConfirm Function once', () => {
|
||||||
|
const result = render(
|
||||||
|
wrapDNDIntl(<ConfirmationDialogBox dialogBox={dialogProps}/>),
|
||||||
|
)
|
||||||
|
|
||||||
|
userEvent.click(result.getByTitle('Confirm'))
|
||||||
|
expect(dialogProps.onConfirm).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirm button (with passed prop text), run onConfirm Function once', () => {
|
||||||
|
const resultWithConfirmBtnText = render(
|
||||||
|
wrapDNDIntl(
|
||||||
|
<ConfirmationDialogBox
|
||||||
|
dialogBox={dialogPropsWithCnfrmBtnText}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
userEvent.click(
|
||||||
|
resultWithConfirmBtnText.getByTitle(dialogPropsWithCnfrmBtnText.confirmButtonText),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(dialogPropsWithCnfrmBtnText.onConfirm).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cancel button click runs onClose function', () => {
|
||||||
|
const result = render(wrapDNDIntl(
|
||||||
|
<ConfirmationDialogBox
|
||||||
|
dialogBox={dialogProps}
|
||||||
|
/>,
|
||||||
|
))
|
||||||
|
|
||||||
|
userEvent.click(result.getByTitle('Cancel'))
|
||||||
|
expect(dialogProps.onClose).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {FormattedMessage} from 'react-intl'
|
import {FormattedMessage} from 'react-intl'
|
||||||
|
|
||||||
import Button from '../widgets/buttons/button'
|
import Button from '../widgets/buttons/button'
|
||||||
|
@ -9,29 +9,40 @@ import Button from '../widgets/buttons/button'
|
||||||
import Dialog from './dialog'
|
import Dialog from './dialog'
|
||||||
import './confirmationDialogBox.scss'
|
import './confirmationDialogBox.scss'
|
||||||
|
|
||||||
|
type ConfirmationDialogBoxProps = {
|
||||||
|
heading: string
|
||||||
|
subText?: string
|
||||||
|
confirmButtonText?: string
|
||||||
|
onConfirm: () => void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
propertyId: string;
|
dialogBox: ConfirmationDialogBoxProps
|
||||||
onClose: () => void;
|
|
||||||
onConfirm: () => void;
|
|
||||||
heading: string;
|
|
||||||
subText?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConfirmationDialogBox = (props: Props) => {
|
export const ConfirmationDialogBox = (props: Props) => {
|
||||||
|
const handleOnClose = useCallback(props.dialogBox.onClose, [])
|
||||||
|
const handleOnConfirm = useCallback(props.dialogBox.onConfirm, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
className='confirmation-dialog-box'
|
className='confirmation-dialog-box'
|
||||||
onClose={props.onClose}
|
onClose={handleOnClose}
|
||||||
>
|
>
|
||||||
<div className='box-area'>
|
<div
|
||||||
<h3 className='heading'>{props.heading}</h3>
|
className='box-area'
|
||||||
<p className='sub-text'>{props.subText}</p>
|
title='Confirmation Dialog Box'
|
||||||
|
>
|
||||||
|
<h3 className='text-heading5'>{props.dialogBox.heading}</h3>
|
||||||
|
<div className='sub-text'>{props.dialogBox.subText}</div>
|
||||||
|
|
||||||
<div className='action-buttons'>
|
<div className='action-buttons'>
|
||||||
<Button
|
<Button
|
||||||
title='Cancel'
|
title='Cancel'
|
||||||
active={true}
|
size='medium'
|
||||||
onClick={props.onClose}
|
emphasis='tertiary'
|
||||||
|
onClick={handleOnClose}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ConfirmationDialog.cancel-action'
|
id='ConfirmationDialog.cancel-action'
|
||||||
|
@ -39,18 +50,24 @@ export const ConfirmationDialogBox = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
title='Delete'
|
title={props.dialogBox.confirmButtonText || 'Confirm'}
|
||||||
|
size='medium'
|
||||||
submit={true}
|
submit={true}
|
||||||
emphasis='danger'
|
emphasis='danger'
|
||||||
onClick={props.onConfirm}
|
onClick={handleOnConfirm}
|
||||||
>
|
>
|
||||||
|
{ props.dialogBox.confirmButtonText ||
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ConfirmationDialog.delete-action'
|
id='ConfirmationDialog.confirm-action'
|
||||||
defaultMessage='Delete'
|
defaultMessage='Confirm'
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ConfirmationDialogBox
|
||||||
|
export {ConfirmationDialogBoxProps}
|
||||||
|
|
|
@ -91,7 +91,7 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||||
expect(container).toMatchSnapshot()
|
expect(container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
test('return kanbanCard and click on delete menu ', () => {
|
test('return kanbanCard and click on delete menu ', () => {
|
||||||
const {container} = render(wrapDNDIntl(
|
const result = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<KanbanCard
|
<KanbanCard
|
||||||
card={card}
|
card={card}
|
||||||
|
@ -105,6 +105,9 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||||
/>
|
/>
|
||||||
</ReduxProvider>,
|
</ReduxProvider>,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
const {container} = result
|
||||||
|
|
||||||
const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})
|
const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})
|
||||||
expect(elementMenuWrapper).not.toBeNull()
|
expect(elementMenuWrapper).not.toBeNull()
|
||||||
userEvent.click(elementMenuWrapper)
|
userEvent.click(elementMenuWrapper)
|
||||||
|
@ -112,8 +115,16 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||||
const elementButtonDelete = within(elementMenuWrapper).getByRole('button', {name: 'Delete'})
|
const elementButtonDelete = within(elementMenuWrapper).getByRole('button', {name: 'Delete'})
|
||||||
expect(elementButtonDelete).not.toBeNull()
|
expect(elementButtonDelete).not.toBeNull()
|
||||||
userEvent.click(elementButtonDelete)
|
userEvent.click(elementButtonDelete)
|
||||||
|
|
||||||
|
const confirmDialog = screen.getByTitle('Confirmation Dialog Box')
|
||||||
|
expect(confirmDialog).toBeDefined()
|
||||||
|
const confirmButton = within(confirmDialog).getByRole('button', {name: 'Delete'})
|
||||||
|
expect(confirmButton).toBeDefined()
|
||||||
|
userEvent.click(confirmButton)
|
||||||
|
|
||||||
expect(mockedMutator.deleteBlock).toBeCalledWith(card, 'delete card')
|
expect(mockedMutator.deleteBlock).toBeCalledWith(card, 'delete card')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('return kanbanCard and click on duplicate menu ', () => {
|
test('return kanbanCard and click on duplicate menu ', () => {
|
||||||
const {container} = render(wrapDNDIntl(
|
const {container} = render(wrapDNDIntl(
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React from 'react'
|
import React, {useState} from 'react'
|
||||||
import {useIntl} from 'react-intl'
|
import {useIntl} from 'react-intl'
|
||||||
|
|
||||||
import {Board, IPropertyTemplate} from '../../blocks/board'
|
import {Board, IPropertyTemplate} from '../../blocks/board'
|
||||||
|
@ -22,6 +22,8 @@ import MenuWrapper from '../../widgets/menuWrapper'
|
||||||
import Tooltip from '../../widgets/tooltip'
|
import Tooltip from '../../widgets/tooltip'
|
||||||
import {sendFlashMessage} from '../flashMessages'
|
import {sendFlashMessage} from '../flashMessages'
|
||||||
import PropertyValueElement from '../propertyValueElement'
|
import PropertyValueElement from '../propertyValueElement'
|
||||||
|
|
||||||
|
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||||
import './kanbanCard.scss'
|
import './kanbanCard.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -49,15 +51,44 @@ const KanbanCard = React.memo((props: Props) => {
|
||||||
const contents = useAppSelector(getCardContents(card.id))
|
const contents = useAppSelector(getCardContents(card.id))
|
||||||
const comments = useAppSelector(getCardComments(card.id))
|
const comments = useAppSelector(getCardComments(card.id))
|
||||||
|
|
||||||
|
const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)
|
||||||
|
const handleDeleteCard = async () => {
|
||||||
|
if (!card) {
|
||||||
|
Utils.assertFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: board.id, card: card.id})
|
||||||
|
await mutator.deleteBlock(card, 'delete card')
|
||||||
|
}
|
||||||
|
const confirmDialogProps: ConfirmationDialogBoxProps = {
|
||||||
|
heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}),
|
||||||
|
confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}),
|
||||||
|
onConfirm: handleDeleteCard,
|
||||||
|
onClose: () => {
|
||||||
|
setShowConfirmationDialogBox(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const handleDeleteButtonOnClick = () => {
|
||||||
|
// user trying to delete a card with blank name
|
||||||
|
// but content present cannot be deleted without
|
||||||
|
// confirmation dialog
|
||||||
|
if (card?.title === '' && card?.fields.contentOrder.length === 0) {
|
||||||
|
handleDeleteCard()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setShowConfirmationDialogBox(true)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
ref={props.readonly ? () => null : cardRef}
|
<div
|
||||||
className={className}
|
ref={props.readonly ? () => null : cardRef}
|
||||||
draggable={!props.readonly}
|
className={className}
|
||||||
style={{opacity: isDragging ? 0.5 : 1}}
|
draggable={!props.readonly}
|
||||||
onClick={props.onClick}
|
style={{opacity: isDragging ? 0.5 : 1}}
|
||||||
>
|
onClick={props.onClick}
|
||||||
{!props.readonly &&
|
>
|
||||||
|
{!props.readonly &&
|
||||||
<MenuWrapper
|
<MenuWrapper
|
||||||
className='optionsMenu'
|
className='optionsMenu'
|
||||||
stopPropagationOnToggle={true}
|
stopPropagationOnToggle={true}
|
||||||
|
@ -68,7 +99,7 @@ const KanbanCard = React.memo((props: Props) => {
|
||||||
icon={<DeleteIcon/>}
|
icon={<DeleteIcon/>}
|
||||||
id='delete'
|
id='delete'
|
||||||
name={intl.formatMessage({id: 'KanbanCard.delete', defaultMessage: 'Delete'})}
|
name={intl.formatMessage({id: 'KanbanCard.delete', defaultMessage: 'Delete'})}
|
||||||
onClick={() => mutator.deleteBlock(card, 'delete card')}
|
onClick={handleDeleteButtonOnClick}
|
||||||
/>
|
/>
|
||||||
<Menu.Text
|
<Menu.Text
|
||||||
icon={<DuplicateIcon/>}
|
icon={<DuplicateIcon/>}
|
||||||
|
@ -107,29 +138,33 @@ const KanbanCard = React.memo((props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className='octo-icontitle'>
|
<div className='octo-icontitle'>
|
||||||
{ card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }
|
{ card.fields.icon ? <div className='octo-icon'>{card.fields.icon}</div> : undefined }
|
||||||
<div key='__title'>{card.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}</div>
|
<div key='__title'>{card.title || intl.formatMessage({id: 'KanbanCard.untitled', defaultMessage: 'Untitled'})}</div>
|
||||||
|
</div>
|
||||||
|
{visiblePropertyTemplates.map((template) => (
|
||||||
|
<Tooltip
|
||||||
|
key={template.id}
|
||||||
|
title={template.name}
|
||||||
|
>
|
||||||
|
<PropertyValueElement
|
||||||
|
board={board}
|
||||||
|
readOnly={true}
|
||||||
|
card={card}
|
||||||
|
contents={contents}
|
||||||
|
comments={comments}
|
||||||
|
propertyTemplate={template}
|
||||||
|
showEmptyPlaceholder={false}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{visiblePropertyTemplates.map((template) => (
|
|
||||||
<Tooltip
|
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||||
key={template.id}
|
|
||||||
title={template.name}
|
</>
|
||||||
>
|
|
||||||
<PropertyValueElement
|
|
||||||
board={board}
|
|
||||||
readOnly={true}
|
|
||||||
card={card}
|
|
||||||
contents={contents}
|
|
||||||
comments={comments}
|
|
||||||
propertyTemplate={template}
|
|
||||||
showEmptyPlaceholder={false}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.emphasis--tertiary {
|
&.emphasis--tertiary {
|
||||||
|
background: rgba(var(--button-bg-rgb), 0.08);
|
||||||
color: rgb(var(--button-bg-rgb));
|
color: rgb(var(--button-bg-rgb));
|
||||||
background-color: rgb(var(--button-bg-rgb), 0.08);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgb(var(--button-bg-rgb), 0.12);
|
background-color: rgb(var(--button-bg-rgb), 0.12);
|
||||||
|
@ -108,6 +108,7 @@
|
||||||
|
|
||||||
&.size--medium {
|
&.size--medium {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue