mirror of
https://github.com/mattermost/focalboard.git
synced 2024-11-27 08:31:20 +02:00
[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
@ -51,9 +51,16 @@
|
||||
"CardDetail.addCardText": "add card text",
|
||||
"CardDetail.moveContent": "move card content",
|
||||
"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-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-name-change-subtext": "type from \"{oldPropType}\" to \"{newPropType}\"",
|
||||
"CardDetailProperty.property-type-change-subtext": "name to \"{newPropName}\"",
|
||||
"CardDialog.copiedLink": "Copied!",
|
||||
"CardDialog.copyLink": "Copy link",
|
||||
"CardDialog.editing-template": "You're editing a template.",
|
||||
@ -62,6 +69,7 @@
|
||||
"Comment.delete": "Delete",
|
||||
"CommentsList.send": "Send",
|
||||
"ConfirmationDialog.cancel-action": "Cancel",
|
||||
"ConfirmationDialog.confirm-action": "Confirm",
|
||||
"ConfirmationDialog.delete-action": "Delete",
|
||||
"ContentBlock.Delete": "Delete",
|
||||
"ContentBlock.DeleteAction": "delete",
|
||||
|
@ -592,6 +592,415 @@ exports[`components/cardDialog return cardDialog menu content 1`] = `
|
||||
</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`] = `
|
||||
<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 count of card which have this property value as not null \\ undefined \\ ''
|
||||
function countNotEmpty(cards: readonly Card[], property: IPropertyTemplate): string {
|
||||
return String(cardsWithValue(cards, property).length)
|
||||
}
|
||||
|
@ -1,5 +1,193 @@
|
||||
// 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`] = `
|
||||
<div>
|
||||
<div
|
||||
@ -42,6 +230,36 @@ exports[`components/cardDetail/CardDetailProperties should match snapshot 1`] =
|
||||
</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"
|
||||
>
|
||||
@ -106,6 +324,36 @@ exports[`components/cardDetail/CardDetailProperties should show property types m
|
||||
</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"
|
||||
>
|
||||
|
@ -1,21 +1,19 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import React from 'react'
|
||||
import {render, screen, act} from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import '@testing-library/jest-dom'
|
||||
import {mocked} from 'ts-jest/utils'
|
||||
|
||||
import '@testing-library/jest-dom'
|
||||
import {createIntl} from 'react-intl'
|
||||
|
||||
import {PropertyType} from '../../blocks/board'
|
||||
import {wrapIntl} from '../../testUtils'
|
||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import mutator from '../../mutator'
|
||||
import {propertyTypesList, typeDisplayName} from '../../widgets/propertyMenu'
|
||||
|
||||
import {PropertyType} from '../../blocks/board'
|
||||
|
||||
import CardDetailProperties from './cardDetailProperties'
|
||||
|
||||
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)
|
||||
@ -56,53 +60,37 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
|
||||
const card = TestBlockFactory.createCard(board)
|
||||
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 cardDetailProps = {
|
||||
board,
|
||||
card,
|
||||
cards,
|
||||
contents: [],
|
||||
comments: [],
|
||||
activeView: view,
|
||||
views,
|
||||
readonly: false,
|
||||
function renderComponent() {
|
||||
const component = wrapIntl((
|
||||
<CardDetailProperties
|
||||
board={board!}
|
||||
card={card}
|
||||
cards={[card]}
|
||||
contents={[]}
|
||||
comments={[]}
|
||||
activeView={view}
|
||||
views={views}
|
||||
readonly={false}
|
||||
/>
|
||||
))
|
||||
|
||||
return render(component)
|
||||
}
|
||||
|
||||
it('should match snapshot', async () => {
|
||||
const {container} = render(
|
||||
wrapIntl(
|
||||
<CardDetailProperties {...cardDetailProps}/>,
|
||||
),
|
||||
)
|
||||
const {container} = renderComponent()
|
||||
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', () => {
|
||||
render(
|
||||
wrapIntl(
|
||||
<CardDetailProperties {...cardDetailProps}/>,
|
||||
),
|
||||
)
|
||||
renderComponent()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: 'Owner'})
|
||||
userEvent.click(menuElement)
|
||||
@ -116,11 +104,7 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
|
||||
it('should show property types menu', () => {
|
||||
const intl = createIntl({locale: 'en'})
|
||||
const {container} = render(
|
||||
wrapIntl(
|
||||
<CardDetailProperties {...cardDetailProps}/>,
|
||||
),
|
||||
)
|
||||
const {container} = renderComponent()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||
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 () => {
|
||||
render(
|
||||
wrapIntl(
|
||||
<CardDetailProperties {...cardDetailProps}/>,
|
||||
),
|
||||
)
|
||||
renderComponent()
|
||||
|
||||
const menuElement = screen.getByRole('button', {name: /add a property/i})
|
||||
userEvent.click(menuElement)
|
||||
@ -151,10 +149,85 @@ describe('components/cardDetail/CardDetailProperties', () => {
|
||||
})
|
||||
|
||||
expect(mockedMutator.insertPropertyTemplate).toHaveBeenCalledTimes(1)
|
||||
|
||||
const args = mockedMutator.insertPropertyTemplate.mock.calls[0]
|
||||
const template = args[3]
|
||||
expect(template).toBeTruthy()
|
||||
expect(template!.name).toMatch(/number/i)
|
||||
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 {ContentBlock} from '../../blocks/contentBlock'
|
||||
import {CommentBlock} from '../../blocks/commentBlock'
|
||||
|
||||
import mutator from '../../mutator'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import PropertyMenu, {PropertyTypes, typeDisplayName} from '../../widgets/propertyMenu'
|
||||
|
||||
import Calculations from '../calculations/calculations'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import {ConfirmationDialogBox} from '../confirmationDialogBox'
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import Menu from '../../widgets/menu'
|
||||
import {IDType, Utils} from '../../utils'
|
||||
@ -42,9 +44,93 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||
}
|
||||
}, [newTemplateId, board.fields.cardProperties])
|
||||
|
||||
const [confirmationDialogBox, setConfirmationDialogBox] = useState<ConfirmationDialogBoxProps>({heading: '', onConfirm: () => {}, onClose: () => {}})
|
||||
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 (
|
||||
<div className='octo-propertylist CardDetailProperties'>
|
||||
@ -63,13 +149,8 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||
propertyId={propertyTemplate.id}
|
||||
propertyName={propertyTemplate.name}
|
||||
propertyType={propertyTemplate.type}
|
||||
onTypeAndNameChanged={(newType: PropertyType, newName: string) => mutator.changePropertyTypeAndName(board, cards, propertyTemplate, newType, newName)}
|
||||
onDelete={(id: string) => {
|
||||
setDeletingPropId(id)
|
||||
setDeletingPropName(propertyTemplate.name)
|
||||
setShowConfirmationDialog(true)
|
||||
}
|
||||
}
|
||||
onTypeAndNameChanged={(newType: PropertyType, newName: string) => onPropertyChangeSetAndOpenConfirmationDialog(newType, newName, propertyTemplate)}
|
||||
onDelete={() => onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate)}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
}
|
||||
@ -88,21 +169,7 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||
|
||||
{showConfirmationDialog && (
|
||||
<ConfirmationDialogBox
|
||||
propertyId={deletingPropId}
|
||||
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})
|
||||
}
|
||||
dialogBox={confirmationDialogBox}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -159,8 +159,58 @@ describe('components/cardDialog', () => {
|
||||
userEvent.click(buttonMenu)
|
||||
const buttonDelete = screen.getByRole('button', {name: 'Delete'})
|
||||
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)
|
||||
})
|
||||
|
||||
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 () => {
|
||||
await act(async () => {
|
||||
render(wrapDNDIntl(
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import React, {useState} from 'react'
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
|
||||
import {Board} from '../blocks/board'
|
||||
@ -17,6 +17,8 @@ import DeleteIcon from '../widgets/icons/delete'
|
||||
import LinkIcon from '../widgets/icons/Link'
|
||||
import Menu from '../widgets/menu'
|
||||
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../components/confirmationDialogBox'
|
||||
|
||||
import CardDetail from './cardDetail/cardDetail'
|
||||
import Dialog from './dialog'
|
||||
import {sendFlashMessage} from './flashMessages'
|
||||
@ -39,6 +41,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
const comments = useAppSelector(getCardComments(props.cardId))
|
||||
const intl = useIntl()
|
||||
|
||||
const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState<boolean>(false)
|
||||
const makeTemplateClicked = async () => {
|
||||
if (!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 = (
|
||||
<Menu position='left'>
|
||||
@ -66,15 +99,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
id='delete'
|
||||
icon={<DeleteIcon/>}
|
||||
name='Delete'
|
||||
onClick={async () => {
|
||||
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()
|
||||
}}
|
||||
onClick={handleDeleteButtonOnClick}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<LinkIcon/>}
|
||||
@ -101,11 +126,12 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
</Menu>
|
||||
)
|
||||
return (
|
||||
<Dialog
|
||||
onClose={props.onClose}
|
||||
toolsMenu={!props.readonly && menu}
|
||||
>
|
||||
{card && card.fields.isTemplate &&
|
||||
<>
|
||||
<Dialog
|
||||
onClose={props.onClose}
|
||||
toolsMenu={!props.readonly && menu}
|
||||
>
|
||||
{card && card.fields.isTemplate &&
|
||||
<div className='banner'>
|
||||
<FormattedMessage
|
||||
id='CardDialog.editing-template'
|
||||
@ -113,7 +139,7 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
/>
|
||||
</div>}
|
||||
|
||||
{card &&
|
||||
{card &&
|
||||
<CardDetail
|
||||
board={board}
|
||||
activeView={activeView}
|
||||
@ -125,14 +151,17 @@ const CardDialog = (props: Props): JSX.Element => {
|
||||
readonly={props.readonly}
|
||||
/>}
|
||||
|
||||
{!card &&
|
||||
{!card &&
|
||||
<div className='banner error'>
|
||||
<FormattedMessage
|
||||
id='CardDialog.nocard'
|
||||
defaultMessage="This card doesn't exist or is inaccessible."
|
||||
/>
|
||||
</div>}
|
||||
</Dialog>
|
||||
</Dialog>
|
||||
|
||||
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
.confirmation-dialog-box {
|
||||
.dialog {
|
||||
.dialog {
|
||||
max-width: 512px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 30%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
z-index: 300;
|
||||
|
||||
@ -25,36 +26,27 @@
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.box-area {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 48px 40px;
|
||||
|
||||
.heading {
|
||||
margin-top: 2rem;
|
||||
padding: 2px 4px;
|
||||
|
||||
.text-heading5 {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
width: 26rem;
|
||||
word-wrap: normal;
|
||||
margin: 0.5rem 3rem;
|
||||
padding: 2px;
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
width: 12rem;
|
||||
}
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
margin: 1rem;
|
||||
justify-content: space-between;
|
||||
|
||||
.Button {
|
||||
margin: 2px 1rem;
|
||||
}
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
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.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
import React, {useCallback} from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import Button from '../widgets/buttons/button'
|
||||
@ -9,29 +9,40 @@ import Button from '../widgets/buttons/button'
|
||||
import Dialog from './dialog'
|
||||
import './confirmationDialogBox.scss'
|
||||
|
||||
type ConfirmationDialogBoxProps = {
|
||||
heading: string
|
||||
subText?: string
|
||||
confirmButtonText?: string
|
||||
onConfirm: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
type Props = {
|
||||
propertyId: string;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
heading: string;
|
||||
subText?: string;
|
||||
dialogBox: ConfirmationDialogBoxProps
|
||||
}
|
||||
|
||||
export const ConfirmationDialogBox = (props: Props) => {
|
||||
const handleOnClose = useCallback(props.dialogBox.onClose, [])
|
||||
const handleOnConfirm = useCallback(props.dialogBox.onConfirm, [])
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className='confirmation-dialog-box'
|
||||
onClose={props.onClose}
|
||||
onClose={handleOnClose}
|
||||
>
|
||||
<div className='box-area'>
|
||||
<h3 className='heading'>{props.heading}</h3>
|
||||
<p className='sub-text'>{props.subText}</p>
|
||||
<div
|
||||
className='box-area'
|
||||
title='Confirmation Dialog Box'
|
||||
>
|
||||
<h3 className='text-heading5'>{props.dialogBox.heading}</h3>
|
||||
<div className='sub-text'>{props.dialogBox.subText}</div>
|
||||
|
||||
<div className='action-buttons'>
|
||||
<Button
|
||||
title='Cancel'
|
||||
active={true}
|
||||
onClick={props.onClose}
|
||||
size='medium'
|
||||
emphasis='tertiary'
|
||||
onClick={handleOnClose}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='ConfirmationDialog.cancel-action'
|
||||
@ -39,18 +50,24 @@ export const ConfirmationDialogBox = (props: Props) => {
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
title='Delete'
|
||||
title={props.dialogBox.confirmButtonText || 'Confirm'}
|
||||
size='medium'
|
||||
submit={true}
|
||||
emphasis='danger'
|
||||
onClick={props.onConfirm}
|
||||
onClick={handleOnConfirm}
|
||||
>
|
||||
{ props.dialogBox.confirmButtonText ||
|
||||
<FormattedMessage
|
||||
id='ConfirmationDialog.delete-action'
|
||||
defaultMessage='Delete'
|
||||
id='ConfirmationDialog.confirm-action'
|
||||
defaultMessage='Confirm'
|
||||
/>
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmationDialogBox
|
||||
export {ConfirmationDialogBoxProps}
|
||||
|
@ -91,7 +91,7 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return kanbanCard and click on delete menu ', () => {
|
||||
const {container} = render(wrapDNDIntl(
|
||||
const result = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<KanbanCard
|
||||
card={card}
|
||||
@ -105,6 +105,9 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
|
||||
const {container} = result
|
||||
|
||||
const elementMenuWrapper = screen.getByRole('button', {name: 'menuwrapper'})
|
||||
expect(elementMenuWrapper).not.toBeNull()
|
||||
userEvent.click(elementMenuWrapper)
|
||||
@ -112,8 +115,16 @@ describe('src/components/kanban/kanbanCard', () => {
|
||||
const elementButtonDelete = within(elementMenuWrapper).getByRole('button', {name: 'Delete'})
|
||||
expect(elementButtonDelete).not.toBeNull()
|
||||
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')
|
||||
})
|
||||
|
||||
test('return kanbanCard and click on duplicate menu ', () => {
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import React, {useState} from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import {Board, IPropertyTemplate} from '../../blocks/board'
|
||||
@ -22,6 +22,8 @@ import MenuWrapper from '../../widgets/menuWrapper'
|
||||
import Tooltip from '../../widgets/tooltip'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
|
||||
import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox'
|
||||
import './kanbanCard.scss'
|
||||
|
||||
type Props = {
|
||||
@ -49,15 +51,44 @@ const KanbanCard = React.memo((props: Props) => {
|
||||
const contents = useAppSelector(getCardContents(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 (
|
||||
<div
|
||||
ref={props.readonly ? () => null : cardRef}
|
||||
className={className}
|
||||
draggable={!props.readonly}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<>
|
||||
<div
|
||||
ref={props.readonly ? () => null : cardRef}
|
||||
className={className}
|
||||
draggable={!props.readonly}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{!props.readonly &&
|
||||
<MenuWrapper
|
||||
className='optionsMenu'
|
||||
stopPropagationOnToggle={true}
|
||||
@ -68,7 +99,7 @@ const KanbanCard = React.memo((props: Props) => {
|
||||
icon={<DeleteIcon/>}
|
||||
id='delete'
|
||||
name={intl.formatMessage({id: 'KanbanCard.delete', defaultMessage: 'Delete'})}
|
||||
onClick={() => mutator.deleteBlock(card, 'delete card')}
|
||||
onClick={handleDeleteButtonOnClick}
|
||||
/>
|
||||
<Menu.Text
|
||||
icon={<DuplicateIcon/>}
|
||||
@ -107,29 +138,33 @@ const KanbanCard = React.memo((props: Props) => {
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
}
|
||||
}
|
||||
|
||||
<div className='octo-icontitle'>
|
||||
{ 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 className='octo-icontitle'>
|
||||
{ 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>
|
||||
{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>
|
||||
{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>
|
||||
|
||||
{showConfirmationDialogBox && <ConfirmationDialogBox dialogBox={confirmDialogProps}/>}
|
||||
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -74,8 +74,8 @@
|
||||
}
|
||||
|
||||
&.emphasis--tertiary {
|
||||
background: rgba(var(--button-bg-rgb), 0.08);
|
||||
color: rgb(var(--button-bg-rgb));
|
||||
background-color: rgb(var(--button-bg-rgb), 0.08);
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(var(--button-bg-rgb), 0.12);
|
||||
@ -108,6 +108,7 @@
|
||||
|
||||
&.size--medium {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 0 20px;
|
||||
height: 40px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user