diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-10-10 17:14:10 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-10-11 20:02:44 +0000 |
commit | 0f4af88bf13a17345b484fb0881acf199f91fa48 (patch) | |
tree | c561c457d7d4f5d6b7e1d3dfcdf35af8ebd41151 /server | |
parent | c289b869b8914d3833cf19fd129de11d53782d93 (diff) | |
download | sonarqube-0f4af88bf13a17345b484fb0881acf199f91fa48.tar.gz sonarqube-0f4af88bf13a17345b484fb0881acf199f91fa48.zip |
SONAR-22298 Fix clipboard a11y
Diffstat (limited to 'server')
20 files changed, 764 insertions, 635 deletions
diff --git a/server/sonar-web/design-system/src/components/DropdownMenu.tsx b/server/sonar-web/design-system/src/components/DropdownMenu.tsx index e3be15c7a1a..1251298bfcb 100644 --- a/server/sonar-web/design-system/src/components/DropdownMenu.tsx +++ b/server/sonar-web/design-system/src/components/DropdownMenu.tsx @@ -29,7 +29,7 @@ import { InputSizeKeys, ThemedProps } from '../types/theme'; import { BaseLink, LinkProps } from './Link'; import NavLink from './NavLink'; import { Tooltip } from './Tooltip'; -import { ClipboardBase } from './clipboard'; +import { useCopyClipboardEffect } from './clipboard'; import { Checkbox } from './input/Checkbox'; interface Props extends React.HtmlHTMLAttributes<HTMLMenuElement> { @@ -223,23 +223,17 @@ interface ItemCopyProps { export function ItemCopy(props: ItemCopyProps) { const { children, className, copyValue, tooltipOverlay } = props; + + const [copySuccess, handleCopy] = useCopyClipboardEffect(copyValue); + return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => ( - <Tooltip content={tooltipOverlay} visible={copySuccess}> - <li role="none"> - <ItemButtonStyled - className={className} - data-clipboard-text={copyValue} - ref={setCopyButton} - role="menuitem" - > - {children} - </ItemButtonStyled> - </li> - </Tooltip> - )} - </ClipboardBase> + <Tooltip content={tooltipOverlay} visible={copySuccess}> + <li role="none"> + <ItemButtonStyled className={className} onClick={handleCopy} role="menuitem"> + {children} + </ItemButtonStyled> + </li> + </Tooltip> ); } diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap index 1d7d505e179..ac2ea24e199 100644 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap @@ -16,16 +16,8 @@ exports[`should highlight code content correctly 1`] = ` border-radius: 0.25rem; } -.emotion-4 { +.emotion-3 { box-sizing: border-box; - -webkit-text-decoration: none; - text-decoration: none; - outline: none; - border: var(--border); - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease; - transition: background-color 0.2s ease; display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -34,19 +26,28 @@ exports[`should highlight code content correctly 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - height: 2.25rem; - font: var(--echoes-typography-text-default-semi-bold); - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-radius: 0.5rem; + padding: var(--echoes-dimension-space-0) var(--button-padding); + height: var(--button-height); + min-height: var(--button-height); + overflow: hidden; + font: var(--echoes-typography-others-label); + color: var(--button-color); + -webkit-text-decoration: none; + text-decoration: none; + background-color: var(--button-background); + border: var(--button-border); + border-radius: var(--echoes-border-radius-400); + outline: none; cursor: pointer; - --background: rgb(255,255,255); - --backgroundHover: rgb(239,242,249); - --color: rgb(62,67,87); - --focus: rgba(197,205,223,0.2); - --border: 1px solid rgb(197,205,223); + --button-color: var(--echoes-color-text-default); + --button-border: var(--echoes-color-border-bold) solid var(--echoes-border-width-default); + --button-background: var(--echoes-color-background-default); + --button-background-hover: var(--echoes-color-background-default-hover); + --button-background-active: var(--echoes-color-background-default-active); + --button-background-focus: var(--echoes-color-background-default); + --button-background-disabled: var(--echoes-color-background-disabled); + --button-padding: var(--echoes-dimension-space-150); + --button-height: var(--echoes-sizes-buttons-large); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -57,90 +58,115 @@ exports[`should highlight code content correctly 1`] = ` position: absolute; } -.emotion-4:hover, -.emotion-4:active { - color: var(--color); - background-color: var(--backgroundHover); +.emotion-3:focus, +.emotion-3:focus-visible { + background-color: var(--button-background-focus); + outline: var(--echoes-color-focus-default) solid var(--echoes-focus-border-width-default); + outline-offset: var(--echoes-focus-border-offset-default); } -.emotion-4:focus, -.emotion-4:active, -.emotion-4:focus-visible { - color: var(--color); +.emotion-3:hover { + background-color: var(--button-background-hover); } -.emotion-4:focus-visible { - outline: var(--echoes-focus-border-width-default) solid var(--echoes-color-focus-default); - outline-offset: var(--echoes-focus-border-offset-default); +.emotion-3:active { + background-color: var(--button-background-active); } -.emotion-4:disabled, -.emotion-4:disabled:hover { +.emotion-3:disabled, +.emotion-3:disabled:has(:hover, :active, :focus, :focus-visible) { color: var(--echoes-color-text-disabled); - background-color: rgb(239,242,249); - border: 1px solid rgb(197,205,223); + background-color: var(--button-background-disabled); + border: none; cursor: not-allowed; + pointer-events: none; } -.emotion-4>svg { - margin-right: 0.25rem; +.code-snippet-highlighted-oneline .emotion-3 { + bottom: 0.5rem; } -.emotion-4 [disabled] { - pointer-events: none; +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: var(--echoes-dimension-space-75); + overflow: hidden; } -.code-snippet-highlighted-oneline .emotion-4 { - bottom: 0.5rem; +.emotion-7 { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-size: calc(1em + 4px); + font-style: normal; + font-weight: normal; + height: calc(2em - 16px); + line-height: calc(2em - 16px); + text-align: center; + vertical-align: bottom; + width: calc(2em - 16px); + font-family: 'Material Symbols Rounded'; +} + +.emotion-9 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.emotion-6 code { +.emotion-11 code { font: var(--echoes-typography-code-default); background: rgb(252,252,253); color: rgb(51,53,60); } -.emotion-6 code.hljs { +.emotion-11 code.hljs { padding: unset; } -.emotion-6 .hljs-meta, -.emotion-6 .hljs-variable { +.emotion-11 .hljs-meta, +.emotion-11 .hljs-variable { color: rgb(51,53,60); } -.emotion-6 .hljs-doctag, -.emotion-6 .hljs-title, -.emotion-6 .hljs-title.class_, -.emotion-6 .hljs-title.function_ { +.emotion-11 .hljs-doctag, +.emotion-11 .hljs-title, +.emotion-11 .hljs-title.class_, +.emotion-11 .hljs-title.function_ { color: rgb(34,84,192); } -.emotion-6 .hljs-comment { +.emotion-11 .hljs-comment { font: var(--echoes-typography-code-comment); color: rgb(109,111,119); } -.emotion-6 .hljs-keyword, -.emotion-6 .hljs-tag, -.emotion-6 .hljs-type { +.emotion-11 .hljs-keyword, +.emotion-11 .hljs-tag, +.emotion-11 .hljs-type { color: rgb(152,29,150); } -.emotion-6 .hljs-literal, -.emotion-6 .hljs-number { +.emotion-11 .hljs-literal, +.emotion-11 .hljs-number { color: rgb(126,83,5); } -.emotion-6 .hljs-string { +.emotion-11 .hljs-string { color: rgb(32,105,31); } -.emotion-6 .hljs-meta .hljs-keyword { +.emotion-11 .hljs-meta .hljs-keyword { color: rgb(47,103,48); } -.emotion-6 .sonar-underline { +.emotion-11 .sonar-underline { -webkit-text-decoration: underline rgb(253,162,155); text-decoration: underline rgb(253,162,155); -webkit-text-decoration: underline rgb(253,162,155) wavy; @@ -149,17 +175,17 @@ exports[`should highlight code content correctly 1`] = ` text-decoration-skip-ink: none; } -.emotion-6.code-wrap { +.emotion-11.code-wrap { white-space: pre-wrap; word-break: break-all; } -.emotion-6.code-wrap.wrap-words { +.emotion-11.code-wrap.wrap-words { word-break: normal; overflow-wrap: break-word; } -.emotion-6 mark { +.emotion-11 mark { font-weight: 400; padding: 0.25rem; border-radius: 0.25rem; @@ -172,32 +198,28 @@ exports[`should highlight code content correctly 1`] = ` class="sw-code fs-mask emotion-0 emotion-1" > <button - aria-describedby="tooltip-3" - class="sw-select-none emotion-2 emotion-3 emotion-4 emotion-5" - data-clipboard-text="<prop>foobar<prop>" + class="sw-select-none emotion-2 emotion-3 emotion-4" + data-state="closed" type="button" > - <svg - aria-hidden="true" - class="octicon octicon-copy" - fill="currentColor" - focusable="false" - height="16" - style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;" - viewBox="0 0 16 16" - width="16" + <span + class="emotion-5 emotion-6" > - <path - d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" - /> - <path - d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" - /> - </svg> - Copy + <span + aria-hidden="true" + class="emotion-7 emotion-8" + > + + </span> + <span + class="emotion-9 emotion-10" + > + Copy + </span> + </span> </button> <span - class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-6 emotion-7" + class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-11 emotion-12" > <pre> <prop>foobar<prop> @@ -223,16 +245,8 @@ exports[`should show full size when multiline with no editing 1`] = ` border-radius: 0.25rem; } -.emotion-4 { +.emotion-3 { box-sizing: border-box; - -webkit-text-decoration: none; - text-decoration: none; - outline: none; - border: var(--border); - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease; - transition: background-color 0.2s ease; display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -241,19 +255,28 @@ exports[`should show full size when multiline with no editing 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - height: 2.25rem; - font: var(--echoes-typography-text-default-semi-bold); - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-radius: 0.5rem; + padding: var(--echoes-dimension-space-0) var(--button-padding); + height: var(--button-height); + min-height: var(--button-height); + overflow: hidden; + font: var(--echoes-typography-others-label); + color: var(--button-color); + -webkit-text-decoration: none; + text-decoration: none; + background-color: var(--button-background); + border: var(--button-border); + border-radius: var(--echoes-border-radius-400); + outline: none; cursor: pointer; - --background: rgb(255,255,255); - --backgroundHover: rgb(239,242,249); - --color: rgb(62,67,87); - --focus: rgba(197,205,223,0.2); - --border: 1px solid rgb(197,205,223); + --button-color: var(--echoes-color-text-default); + --button-border: var(--echoes-color-border-bold) solid var(--echoes-border-width-default); + --button-background: var(--echoes-color-background-default); + --button-background-hover: var(--echoes-color-background-default-hover); + --button-background-active: var(--echoes-color-background-default-active); + --button-background-focus: var(--echoes-color-background-default); + --button-background-disabled: var(--echoes-color-background-disabled); + --button-padding: var(--echoes-dimension-space-150); + --button-height: var(--echoes-sizes-buttons-large); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -264,90 +287,115 @@ exports[`should show full size when multiline with no editing 1`] = ` position: absolute; } -.emotion-4:hover, -.emotion-4:active { - color: var(--color); - background-color: var(--backgroundHover); +.emotion-3:focus, +.emotion-3:focus-visible { + background-color: var(--button-background-focus); + outline: var(--echoes-color-focus-default) solid var(--echoes-focus-border-width-default); + outline-offset: var(--echoes-focus-border-offset-default); } -.emotion-4:focus, -.emotion-4:active, -.emotion-4:focus-visible { - color: var(--color); +.emotion-3:hover { + background-color: var(--button-background-hover); } -.emotion-4:focus-visible { - outline: var(--echoes-focus-border-width-default) solid var(--echoes-color-focus-default); - outline-offset: var(--echoes-focus-border-offset-default); +.emotion-3:active { + background-color: var(--button-background-active); } -.emotion-4:disabled, -.emotion-4:disabled:hover { +.emotion-3:disabled, +.emotion-3:disabled:has(:hover, :active, :focus, :focus-visible) { color: var(--echoes-color-text-disabled); - background-color: rgb(239,242,249); - border: 1px solid rgb(197,205,223); + background-color: var(--button-background-disabled); + border: none; cursor: not-allowed; + pointer-events: none; } -.emotion-4>svg { - margin-right: 0.25rem; +.code-snippet-highlighted-oneline .emotion-3 { + bottom: 0.5rem; } -.emotion-4 [disabled] { - pointer-events: none; +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: var(--echoes-dimension-space-75); + overflow: hidden; } -.code-snippet-highlighted-oneline .emotion-4 { - bottom: 0.5rem; +.emotion-7 { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-size: calc(1em + 4px); + font-style: normal; + font-weight: normal; + height: calc(2em - 16px); + line-height: calc(2em - 16px); + text-align: center; + vertical-align: bottom; + width: calc(2em - 16px); + font-family: 'Material Symbols Rounded'; } -.emotion-6 code { +.emotion-9 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.emotion-11 code { font: var(--echoes-typography-code-default); background: rgb(252,252,253); color: rgb(51,53,60); } -.emotion-6 code.hljs { +.emotion-11 code.hljs { padding: unset; } -.emotion-6 .hljs-meta, -.emotion-6 .hljs-variable { +.emotion-11 .hljs-meta, +.emotion-11 .hljs-variable { color: rgb(51,53,60); } -.emotion-6 .hljs-doctag, -.emotion-6 .hljs-title, -.emotion-6 .hljs-title.class_, -.emotion-6 .hljs-title.function_ { +.emotion-11 .hljs-doctag, +.emotion-11 .hljs-title, +.emotion-11 .hljs-title.class_, +.emotion-11 .hljs-title.function_ { color: rgb(34,84,192); } -.emotion-6 .hljs-comment { +.emotion-11 .hljs-comment { font: var(--echoes-typography-code-comment); color: rgb(109,111,119); } -.emotion-6 .hljs-keyword, -.emotion-6 .hljs-tag, -.emotion-6 .hljs-type { +.emotion-11 .hljs-keyword, +.emotion-11 .hljs-tag, +.emotion-11 .hljs-type { color: rgb(152,29,150); } -.emotion-6 .hljs-literal, -.emotion-6 .hljs-number { +.emotion-11 .hljs-literal, +.emotion-11 .hljs-number { color: rgb(126,83,5); } -.emotion-6 .hljs-string { +.emotion-11 .hljs-string { color: rgb(32,105,31); } -.emotion-6 .hljs-meta .hljs-keyword { +.emotion-11 .hljs-meta .hljs-keyword { color: rgb(47,103,48); } -.emotion-6 .sonar-underline { +.emotion-11 .sonar-underline { -webkit-text-decoration: underline rgb(253,162,155); text-decoration: underline rgb(253,162,155); -webkit-text-decoration: underline rgb(253,162,155) wavy; @@ -356,17 +404,17 @@ exports[`should show full size when multiline with no editing 1`] = ` text-decoration-skip-ink: none; } -.emotion-6.code-wrap { +.emotion-11.code-wrap { white-space: pre-wrap; word-break: break-all; } -.emotion-6.code-wrap.wrap-words { +.emotion-11.code-wrap.wrap-words { word-break: normal; overflow-wrap: break-word; } -.emotion-6 mark { +.emotion-11 mark { font-weight: 400; padding: 0.25rem; border-radius: 0.25rem; @@ -379,33 +427,28 @@ exports[`should show full size when multiline with no editing 1`] = ` class="sw-code fs-mask emotion-0 emotion-1" > <button - aria-describedby="tooltip-1" - class="sw-select-none emotion-2 emotion-3 emotion-4 emotion-5" - data-clipboard-text="foo -bar" + class="sw-select-none emotion-2 emotion-3 emotion-4" + data-state="closed" type="button" > - <svg - aria-hidden="true" - class="octicon octicon-copy" - fill="currentColor" - focusable="false" - height="16" - style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;" - viewBox="0 0 16 16" - width="16" + <span + class="emotion-5 emotion-6" > - <path - d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" - /> - <path - d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" - /> - </svg> - Copy + <span + aria-hidden="true" + class="emotion-7 emotion-8" + > + + </span> + <span + class="emotion-9 emotion-10" + > + Copy + </span> + </span> </button> <span - class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-6 emotion-7" + class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-11 emotion-12" > <pre> foo @@ -432,16 +475,8 @@ exports[`should show reduced size when single line with no editing 1`] = ` border-radius: 0.25rem; } -.emotion-4 { +.emotion-3 { box-sizing: border-box; - -webkit-text-decoration: none; - text-decoration: none; - outline: none; - border: var(--border); - color: var(--color); - background-color: var(--background); - -webkit-transition: background-color 0.2s ease; - transition: background-color 0.2s ease; display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -450,19 +485,28 @@ exports[`should show reduced size when single line with no editing 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - height: 2.25rem; - font: var(--echoes-typography-text-default-semi-bold); - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-radius: 0.5rem; + padding: var(--echoes-dimension-space-0) var(--button-padding); + height: var(--button-height); + min-height: var(--button-height); + overflow: hidden; + font: var(--echoes-typography-others-label); + color: var(--button-color); + -webkit-text-decoration: none; + text-decoration: none; + background-color: var(--button-background); + border: var(--button-border); + border-radius: var(--echoes-border-radius-400); + outline: none; cursor: pointer; - --background: rgb(255,255,255); - --backgroundHover: rgb(239,242,249); - --color: rgb(62,67,87); - --focus: rgba(197,205,223,0.2); - --border: 1px solid rgb(197,205,223); + --button-color: var(--echoes-color-text-default); + --button-border: var(--echoes-color-border-bold) solid var(--echoes-border-width-default); + --button-background: var(--echoes-color-background-default); + --button-background-hover: var(--echoes-color-background-default-hover); + --button-background-active: var(--echoes-color-background-default-active); + --button-background-focus: var(--echoes-color-background-default); + --button-background-disabled: var(--echoes-color-background-disabled); + --button-padding: var(--echoes-dimension-space-150); + --button-height: var(--echoes-sizes-buttons-large); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -475,90 +519,115 @@ exports[`should show reduced size when single line with no editing 1`] = ` top: 1rem; } -.emotion-4:hover, -.emotion-4:active { - color: var(--color); - background-color: var(--backgroundHover); +.emotion-3:focus, +.emotion-3:focus-visible { + background-color: var(--button-background-focus); + outline: var(--echoes-color-focus-default) solid var(--echoes-focus-border-width-default); + outline-offset: var(--echoes-focus-border-offset-default); } -.emotion-4:focus, -.emotion-4:active, -.emotion-4:focus-visible { - color: var(--color); +.emotion-3:hover { + background-color: var(--button-background-hover); } -.emotion-4:focus-visible { - outline: var(--echoes-focus-border-width-default) solid var(--echoes-color-focus-default); - outline-offset: var(--echoes-focus-border-offset-default); +.emotion-3:active { + background-color: var(--button-background-active); } -.emotion-4:disabled, -.emotion-4:disabled:hover { +.emotion-3:disabled, +.emotion-3:disabled:has(:hover, :active, :focus, :focus-visible) { color: var(--echoes-color-text-disabled); - background-color: rgb(239,242,249); - border: 1px solid rgb(197,205,223); + background-color: var(--button-background-disabled); + border: none; cursor: not-allowed; + pointer-events: none; } -.emotion-4>svg { - margin-right: 0.25rem; +.code-snippet-highlighted-oneline .emotion-3 { + bottom: 0.5rem; } -.emotion-4 [disabled] { - pointer-events: none; +.emotion-5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: var(--echoes-dimension-space-75); + overflow: hidden; } -.code-snippet-highlighted-oneline .emotion-4 { - bottom: 0.5rem; +.emotion-7 { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-size: calc(1em + 4px); + font-style: normal; + font-weight: normal; + height: calc(2em - 16px); + line-height: calc(2em - 16px); + text-align: center; + vertical-align: bottom; + width: calc(2em - 16px); + font-family: 'Material Symbols Rounded'; +} + +.emotion-9 { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.emotion-6 code { +.emotion-11 code { font: var(--echoes-typography-code-default); background: rgb(252,252,253); color: rgb(51,53,60); } -.emotion-6 code.hljs { +.emotion-11 code.hljs { padding: unset; } -.emotion-6 .hljs-meta, -.emotion-6 .hljs-variable { +.emotion-11 .hljs-meta, +.emotion-11 .hljs-variable { color: rgb(51,53,60); } -.emotion-6 .hljs-doctag, -.emotion-6 .hljs-title, -.emotion-6 .hljs-title.class_, -.emotion-6 .hljs-title.function_ { +.emotion-11 .hljs-doctag, +.emotion-11 .hljs-title, +.emotion-11 .hljs-title.class_, +.emotion-11 .hljs-title.function_ { color: rgb(34,84,192); } -.emotion-6 .hljs-comment { +.emotion-11 .hljs-comment { font: var(--echoes-typography-code-comment); color: rgb(109,111,119); } -.emotion-6 .hljs-keyword, -.emotion-6 .hljs-tag, -.emotion-6 .hljs-type { +.emotion-11 .hljs-keyword, +.emotion-11 .hljs-tag, +.emotion-11 .hljs-type { color: rgb(152,29,150); } -.emotion-6 .hljs-literal, -.emotion-6 .hljs-number { +.emotion-11 .hljs-literal, +.emotion-11 .hljs-number { color: rgb(126,83,5); } -.emotion-6 .hljs-string { +.emotion-11 .hljs-string { color: rgb(32,105,31); } -.emotion-6 .hljs-meta .hljs-keyword { +.emotion-11 .hljs-meta .hljs-keyword { color: rgb(47,103,48); } -.emotion-6 .sonar-underline { +.emotion-11 .sonar-underline { -webkit-text-decoration: underline rgb(253,162,155); text-decoration: underline rgb(253,162,155); -webkit-text-decoration: underline rgb(253,162,155) wavy; @@ -567,17 +636,17 @@ exports[`should show reduced size when single line with no editing 1`] = ` text-decoration-skip-ink: none; } -.emotion-6.code-wrap { +.emotion-11.code-wrap { white-space: pre-wrap; word-break: break-all; } -.emotion-6.code-wrap.wrap-words { +.emotion-11.code-wrap.wrap-words { word-break: normal; overflow-wrap: break-word; } -.emotion-6 mark { +.emotion-11 mark { font-weight: 400; padding: 0.25rem; border-radius: 0.25rem; @@ -590,32 +659,28 @@ exports[`should show reduced size when single line with no editing 1`] = ` class="sw-code sw-py-6 code-snippet-highlighted-oneline fs-mask emotion-0 emotion-1" > <button - aria-describedby="tooltip-2" - class="sw-select-none emotion-2 emotion-3 emotion-4 emotion-5" - data-clipboard-text="foobar" + class="sw-select-none emotion-2 emotion-3 emotion-4" + data-state="closed" type="button" > - <svg - aria-hidden="true" - class="octicon octicon-copy" - fill="currentColor" - focusable="false" - height="16" - style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;" - viewBox="0 0 16 16" - width="16" + <span + class="emotion-5 emotion-6" > - <path - d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" - /> - <path - d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" - /> - </svg> - Copy + <span + aria-hidden="true" + class="emotion-7 emotion-8" + > + + </span> + <span + class="emotion-9 emotion-10" + > + Copy + </span> + </span> </button> <span - class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-6 emotion-7" + class="hljs sw-overflow-auto sw-pr-24 sw-flex emotion-11 emotion-12" > <code> foobar diff --git a/server/sonar-web/design-system/src/components/__tests__/clipboard-test.tsx b/server/sonar-web/design-system/src/components/__tests__/clipboard-test.tsx index 027b990fff6..e073810552e 100644 --- a/server/sonar-web/design-system/src/components/__tests__/clipboard-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/clipboard-test.tsx @@ -43,9 +43,9 @@ describe('ClipboardButton', () => { await user.click(screen.getByRole('button', { name: 'Copy' })); - expect(await screen.findByText('Copied')).toBeVisible(); + expect(await screen.findByRole('tooltip', { name: 'Copied' })).toBeInTheDocument(); - await waitForElementToBeRemoved(() => screen.queryByText('Copied')); + await waitForElementToBeRemoved(() => screen.queryByRole('tooltip', { name: 'Copied' })); jest.runAllTimers(); }); @@ -74,9 +74,9 @@ describe('ClipboardIconButton', () => { await user.click(screen.getByRole('button', { name: 'Copy to clipboard' })); - expect(await screen.findByText('Copied')).toBeVisible(); + expect(await screen.findByRole('tooltip', { name: 'Copied' })).toBeInTheDocument(); - await waitForElementToBeRemoved(() => screen.queryByText('Copied')); + await waitForElementToBeRemoved(() => screen.queryByRole('tooltip', { name: 'Copied' })); jest.runAllTimers(); }); }); diff --git a/server/sonar-web/design-system/src/components/clipboard.tsx b/server/sonar-web/design-system/src/components/clipboard.tsx index 1745e74f2fe..530880b86c6 100644 --- a/server/sonar-web/design-system/src/components/clipboard.tsx +++ b/server/sonar-web/design-system/src/components/clipboard.tsx @@ -17,91 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + Button, + ButtonIcon, + ButtonSize, + ButtonVariety, + IconCopy, + Tooltip, + TooltipProvider, +} from '@sonarsource/echoes-react'; import classNames from 'classnames'; -import Clipboard from 'clipboard'; -import React from 'react'; -import { INTERACTIVE_TOOLTIP_DELAY } from '../helpers/constants'; -import { ButtonSecondary } from '../sonar-aligned/components/buttons'; -import { DiscreetInteractiveIcon, InteractiveIcon, InteractiveIconSize } from './InteractiveIcon'; -import { Tooltip } from './Tooltip'; -import { CopyIcon } from './icons/CopyIcon'; -import { IconProps } from './icons/Icon'; +import { copy } from 'clipboard'; +import React, { ComponentProps, useCallback, useState } from 'react'; const COPY_SUCCESS_NOTIFICATION_LIFESPAN = 1000; -export interface State { - copySuccess: boolean; -} - -interface RenderProps { - copySuccess: boolean; - setCopyButton: (node: HTMLElement | null) => void; -} - -interface BaseProps { - children: (props: RenderProps) => React.ReactNode; -} - -export class ClipboardBase extends React.PureComponent<BaseProps, State> { - private clipboard?: Clipboard; - private copyButton?: HTMLElement | null; - mounted = false; - state: State = { copySuccess: false }; - - componentDidMount() { - this.mounted = true; - if (this.copyButton) { - this.clipboard = new Clipboard(this.copyButton, { - container: this.copyButton.parentElement ?? undefined, - }); - this.clipboard.on('success', this.handleSuccessCopy); - } - } - - componentDidUpdate(props: BaseProps) { - if (this.props.children !== props.children) { - if (this.clipboard) { - this.clipboard.destroy(); - } - if (this.copyButton) { - this.clipboard = new Clipboard(this.copyButton, { - container: this.copyButton.parentElement ?? undefined, - }); - this.clipboard.on('success', this.handleSuccessCopy); - } - } - } - - componentWillUnmount() { - this.mounted = false; - if (this.clipboard) { - this.clipboard.destroy(); - } - } - - setCopyButton = (node: HTMLElement | null) => { - this.copyButton = node; - }; - - handleSuccessCopy = () => { - if (this.mounted) { - this.setState({ copySuccess: true }); - setTimeout(() => { - if (this.mounted) { - this.setState({ copySuccess: false }); - } - }, COPY_SUCCESS_NOTIFICATION_LIFESPAN); - } - }; - - render() { - return this.props.children({ - setCopyButton: this.setCopyButton, - copySuccess: this.state.copySuccess, - }); - } -} - interface ButtonProps { children?: React.ReactNode; className?: string; @@ -111,41 +41,42 @@ interface ButtonProps { icon?: React.ReactNode; } -export function ClipboardButton({ - icon = <CopyIcon />, - className, - children, - copyValue, - copiedLabel = 'Copied', - copyLabel = 'Copy', -}: ButtonProps) { +export function ClipboardButton(props: ButtonProps) { + const { + icon = <IconCopy />, + className, + children, + copyValue, + copiedLabel = 'Copied', + copyLabel = 'Copy', + } = props; + const [copySuccess, handleCopy] = useCopyClipboardEffect(copyValue); + return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => ( - <Tooltip content={copiedLabel} visible={copySuccess}> - <ButtonSecondary - className={classNames('sw-select-none', className)} - data-clipboard-text={copyValue} - icon={icon} - ref={setCopyButton} - > - {children ?? copyLabel} - </ButtonSecondary> - </Tooltip> - )} - </ClipboardBase> + <TooltipProvider> + {/* TODO ^ Remove TooltipProvider after design-system is reintegrated into sonar-web */} + <Tooltip content={copiedLabel} isOpen={copySuccess}> + <Button + className={classNames('sw-select-none', className)} + onClick={handleCopy} + prefix={icon} + > + {children ?? copyLabel} + </Button> + </Tooltip> + </TooltipProvider> ); } interface IconButtonProps { - Icon?: React.ComponentType<React.PropsWithChildren<IconProps>>; + Icon?: ComponentProps<typeof ButtonIcon>['Icon']; 'aria-label'?: string; className?: string; copiedLabel?: string; copyLabel?: string; copyValue: string; discreet?: boolean; - size?: InteractiveIconSize; + size?: ButtonSize; } export function ClipboardIconButton(props: IconButtonProps) { @@ -153,37 +84,49 @@ export function ClipboardIconButton(props: IconButtonProps) { className, copyValue, discreet, - size = 'small', - Icon = CopyIcon, + size = ButtonSize.Medium, + Icon = IconCopy, copiedLabel = 'Copied', copyLabel = 'Copy to clipboard', } = props; - const InteractiveIconComponent = discreet ? DiscreetInteractiveIcon : InteractiveIcon; + + const [copySuccess, handleCopy] = useCopyClipboardEffect(copyValue); return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => { - return ( - <Tooltip - content={ - <div className="sw-w-abs-150 sw-text-center"> - {copySuccess ? copiedLabel : copyLabel} - </div> - } - mouseEnterDelay={INTERACTIVE_TOOLTIP_DELAY} - {...(copySuccess ? { visible: copySuccess } : undefined)} - > - <InteractiveIconComponent - Icon={Icon} - aria-label={props['aria-label'] ?? copyLabel} - className={className} - data-clipboard-text={copyValue} - ref={setCopyButton} - size={size} - /> - </Tooltip> - ); - }} - </ClipboardBase> + <TooltipProvider> + {/* TODO ^ Remove TooltipProvider after design-system is reintegrated into sonar-web */} + <ButtonIcon + Icon={Icon} + ariaLabel={props['aria-label'] ?? copyLabel} + className={className} + onClick={handleCopy} + size={size} + tooltipContent={copySuccess ? copiedLabel : copyLabel} + tooltipOptions={copySuccess ? { isOpen: copySuccess } : undefined} + variety={discreet ? ButtonVariety.DefaultGhost : ButtonVariety.Default} + /> + </TooltipProvider> ); } + +export function useCopyClipboardEffect(copyValue: string) { + const [copySuccess, setCopySuccess] = useState(false); + + const handleCopy = useCallback( + ({ currentTarget }: React.MouseEvent<HTMLButtonElement>) => { + const isSuccess = copy(copyValue) === copyValue; + setCopySuccess(isSuccess); + + if (isSuccess) { + setTimeout(() => { + setCopySuccess(false); + }, COPY_SUCCESS_NOTIFICATION_LIFESPAN); + } + + currentTarget.focus(); + }, + [copyValue], + ); + + return [copySuccess, handleCopy] as const; +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsHeader.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsHeader.tsx index 5172425030d..abfad7d1414 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsHeader.tsx @@ -17,7 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ClipboardIconButton, IssueMessageHighlighting, LinkIcon, Title } from 'design-system'; +import { IconLink } from '@sonarsource/echoes-react'; +import { ClipboardIconButton, IssueMessageHighlighting, Title } from 'design-system'; import * as React from 'react'; import { translate } from '../../../helpers/l10n'; import { getPathUrlAsString, getRuleUrl } from '../../../helpers/urls'; @@ -44,7 +45,7 @@ export default function RuleDetailsMeta(props: Readonly<Props>) { <Title className="sw-mb-0"> <IssueMessageHighlighting message={ruleDetails.name} /> <ClipboardIconButton - Icon={LinkIcon} + Icon={IconLink} aria-label={translate('permalink')} className="sw-ml-1 sw-align-bottom" copyValue={getPathUrlAsString(ruleUrl, ruleDetails.isExternal)} diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx index f249c8f48e4..e16b6bb0182 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx @@ -42,10 +42,6 @@ it('renders correctly', async () => { // Title expect(byRole('heading', { name: issue.message }).get()).toBeInTheDocument(); - expect(byRole('button', { name: 'permalink' }).get()).toHaveAttribute( - 'data-clipboard-text', - 'http://localhost/project/issues?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject', - ); // CCT attribute const cctBadge = byText( diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx index c9d1aa14aa0..452e18f2faa 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx @@ -17,13 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { IconLink } from '@sonarsource/echoes-react'; import { Badge, BasicSeparator, ClipboardIconButton, IssueMessageHighlighting, Link, - LinkIcon, Note, PageContentFontWrapper, } from 'design-system'; @@ -172,7 +172,7 @@ export default class IssueHeader extends React.PureComponent<Props, State> { messageFormattings={issue.messageFormattings} /> <ClipboardIconButton - Icon={LinkIcon} + Icon={IconLink} aria-label={translate('permalink')} className="sw-ml-1 sw-align-bottom" copyValue={getPathUrlAsString(issueUrl, false)} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx index 34fd6605f0f..efa7c66293e 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx @@ -17,13 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { IconLink } from '@sonarsource/echoes-react'; import { ClipboardIconButton, IssueMessageHighlighting, LightLabel, LightPrimary, Link, - LinkIcon, StyledPageTitle, } from 'design-system'; import React from 'react'; @@ -73,7 +73,7 @@ export function HotspotHeader(props: HotspotHeaderProps) { <IssueMessageHighlighting message={message} messageFormattings={messageFormattings} /> </LightPrimary> <ClipboardIconButton - Icon={LinkIcon} + Icon={IconLink} copiedLabel={translate('copied_action')} copyLabel={translate('copy_to_clipboard')} className="sw-ml-2" diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx index fca8c5f30a1..cc652ed6323 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/__tests__/EncryptionApp-it.tsx @@ -19,7 +19,7 @@ */ import userEvent from '@testing-library/user-event'; import React from 'react'; -import { byRole } from '~sonar-aligned/helpers/testSelector'; +import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import EncryptionApp from '../EncryptionApp'; @@ -50,7 +50,7 @@ it('should be able to generate new key', async () => { expect(await ui.appHeading.find()).toBeInTheDocument(); await user.click(ui.generateSecretButton.get()); - expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'secretKey'); + expect(byText('secretKey').get()).toBeInTheDocument(); }); it('should be able to encrypt property value when secret is registered', async () => { @@ -61,11 +61,11 @@ it('should be able to encrypt property value when secret is registered', async ( expect(await ui.appHeading.find()).toBeInTheDocument(); await user.type(ui.encryptTextarea.get(), 'sonar.announcement.message'); await user.click(ui.encryptButton.get()); - expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'encryptedValue'); + expect(byText('encryptedValue').get()).toBeInTheDocument(); // can generate new secret in view await user.click(ui.generateNewSecretButton.get()); - expect(ui.copyToClipboard.get()).toHaveAttribute('data-clipboard-text', 'secretKey'); + expect(byText('secretKey').get()).toBeInTheDocument(); }); function renderEncryptionApp() { diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx index e0e59af446c..756edf56855 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx @@ -39,10 +39,7 @@ describe('System Info Standalone', () => { renderSystemApp(); await ui.appIsLoaded(); - expect(ui.copyIdInformation.get()).toHaveAttribute( - 'data-clipboard-text', - expect.stringContaining(`Server ID: asd564-asd54a-5dsfg45`), - ); + expect(byText('asd564-asd54a-5dsfg45').get()).toBeInTheDocument(); expect(ui.sectionButton('System').get()).toBeInTheDocument(); expect(screen.queryByRole('cell', { name: 'High Availability' })).not.toBeInTheDocument(); @@ -103,10 +100,7 @@ describe('System Info Cluster', () => { expect(ui.downloadLogsButton.query()).not.toBeInTheDocument(); expect(ui.downloadSystemInfoButton.get()).toBeInTheDocument(); - expect(ui.copyIdInformation.get()).toHaveAttribute( - 'data-clipboard-text', - expect.stringContaining(`Server ID: asd564-asd54a-5dsfg45`), - ); + expect(byText('asd564-asd54a-5dsfg45').get()).toBeInTheDocument(); // Renders health checks expect(ui.healthCauseWarning.get()).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx index c649604f45b..e5ea1b7114a 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { queryHelpers, screen, within } from '@testing-library/react'; +import { screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { byLabelText } from '~sonar-aligned/helpers/testSelector'; @@ -79,23 +79,13 @@ it('should show a permalink on line number', async () => { ); expect( - /* eslint-disable-next-line testing-library/prefer-presence-queries */ - queryHelpers.queryByAttribute( - 'data-clipboard-text', - row, - 'http://localhost/code?id=foo&selected=foo%3Atest1.js&line=1', - ), + rowScreen.getByRole('menuitem', { name: 'source_viewer.copy_permalink' }), ).toBeInTheDocument(); await user.keyboard('[Escape]'); expect( - /* eslint-disable-next-line testing-library/prefer-presence-queries */ - queryHelpers.queryByAttribute( - 'data-clipboard-text', - row, - 'http://localhost/code?id=foo&selected=foo%3Atest1.js&line=1', - ), + rowScreen.queryByRole('menuitem', { name: 'source_viewer.copy_permalink' }), ).not.toBeInTheDocument(); row = await screen.findByRole('row', { name: / \* 6$/ }); diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx index 13b851236a7..7837ca6c18f 100644 --- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx @@ -150,7 +150,9 @@ function assertServiceEndpointStepIsCorrectlyRendered() { name: 'onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title', }), ).toBeInTheDocument(); - expect(getCopyToClipboardValue(0, 'Copy to clipboard')).toBe('https://sonarqube.example.com/'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy to clipboard', inlineSnippet: true })).toBe( + 'https://sonarqube.example.com/', + ); expect( screen.getByRole('button', { name: 'onboarding.token.generate.long' }), ).toBeInTheDocument(); @@ -163,26 +165,34 @@ function assertDotNetStepIsCorrectlyRendered() { }), ).toBeInTheDocument(); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toBe('foo'); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true })).toBe( + 'foo', + ); } function assertMavenStepIsCorrectlyRendered() { - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('maven, copy additional properties'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'maven, copy additional properties', + ); } function assertGradleStepIsCorrectlyRendered() { - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('gradle, copy additional properties'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'gradle, copy additional properties', + ); } function assertObjCStepIsCorrectlyRendered(os: string, arch: string = 'x86_64') { - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( `objectivec ${os} ${arch}, copy shell script`, ); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toBe('foo'); - expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot( - `objectivec ${os} ${arch}, copy additional properties`, + expect(getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true })).toBe( + 'foo', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect( + getCopyToClipboardValue({ i: 2, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot(`objectivec ${os} ${arch}, copy additional properties`); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( `objectivec ${os} ${arch}, copy build-wrapper command`, ); } @@ -192,20 +202,24 @@ function assertAutomaticCppStepIsCorrectlyRendered() { } function assertManualCppStepIsCorrectlyRendered(os: string, arch: string = 'x86_64') { - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( `manual-cpp ${os} ${arch}, copy shell script`, ); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toBe('foo'); - expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot( - `manual-cpp ${os} ${arch}, copy additional properties`, + expect(getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true })).toBe( + 'foo', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect( + getCopyToClipboardValue({ i: 2, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot(`manual-cpp ${os} ${arch}, copy additional properties`); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( `manual-cpp ${os} ${arch}, copy build-wrapper command`, ); } function assertOtherStepIsCorrectlyRendered() { - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toBe('foo'); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true })).toBe( + 'foo', + ); } function assertFinishStepIsCorrectlyRendered() { diff --git a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx index 54ecf78154a..e4a6c586fde 100644 --- a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx @@ -30,6 +30,7 @@ import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; import { getCommonNodes, + getCopyToClipboardHostURLValue, getCopyToClipboardValue, getTutorialActionButtons, getTutorialBuildButtons, @@ -64,81 +65,97 @@ it('should follow and complete all steps', async () => { expect(await ui.secretsStepTitle.find()).toBeInTheDocument(); // Env variables step - expect(getCopyToClipboardValue(0, 'Copy to clipboard')).toMatchSnapshot('sonar token key'); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toMatchSnapshot('sonarqube host url key'); - expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot( + expect( + getCopyToClipboardValue({ i: 0, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot('sonar token key'); + expect( + getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot('sonarqube host url key'); + expect(getCopyToClipboardHostURLValue({ i: 2, name: 'Copy to clipboard' })).toMatchSnapshot( 'sonarqube host url value', ); // Create/update configuration file step // Maven await user.click(ui.mavenBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Maven: bitbucket-pipelines.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Maven: bitbucket-pipelines.yml', + ); // Gradle await user.click(ui.gradleBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Groovy: build.gradle'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('Groovy: build.gradle'); await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Kotlin: build.gradle.kts'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Gradle: bitbucket-pipelines.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Kotlin: build.gradle.kts', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Gradle: bitbucket-pipelines.yml', + ); // .NET await user.click(ui.dotnetBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('.NET: bitbucket-pipelines.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + '.NET: bitbucket-pipelines.yml', + ); // Cpp await user.click(ui.cppBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: bitbucket-pipelines.yml', ); // Cpp (manual) await user.click(ui.autoConfigManual.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (manual) and Objective-C: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (manual) and Objective-C: bitbucket-pipelines.yml', ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (manual arm64) and Objective-C: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (manual arm64) and Objective-C: bitbucket-pipelines.yml', ); // Objective-C await user.click(ui.objCBuildButton.get()); await user.click(ui.x86_64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (manual) and Objective-C: bitbucket-pipelines.yml', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (manual) and Objective-C: sonar-project.properties', ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (manual arm64) and Objective-C: bitbucket-pipelines.yml', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (manual arm64) and Objective-C: sonar-project.properties', ); // Dart await user.click(ui.dartBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart: sonar-project.properties'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Dart: bitbucket-pipelines.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart: sonar-project.properties', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Dart: bitbucket-pipelines.yml', + ); // Other await user.click(ui.otherBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: .github/workflows/build.yml', ); @@ -154,7 +171,7 @@ it('should generate/delete a new token or use existing one', async () => { // Generate token await user.click(ui.genTokenDialogButton.get()); await user.click(ui.generateTokenButton.get()); - expect(getCopyToClipboardValue()).toEqual('generatedtoken2'); + expect(getCopyToClipboardValue({ inlineSnippet: true })).toEqual('generatedtoken2'); // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); diff --git a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/__snapshots__/BitbucketPipelinesTutorial-it.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/__snapshots__/BitbucketPipelinesTutorial-it.tsx.snap index f4549f87087..797e6fd2b08 100644 --- a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/__snapshots__/BitbucketPipelinesTutorial-it.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/__snapshots__/BitbucketPipelinesTutorial-it.tsx.snap @@ -30,8 +30,7 @@ pipelines: `; exports[`should follow and complete all steps: C++ (automatic) and other: .github/workflows/build.yml 1`] = ` -" -definitions: +"definitions: steps: - step: &build-step name: SonarQube analysis @@ -58,8 +57,7 @@ pipelines: `; exports[`should follow and complete all steps: C++ (automatic) and other: bitbucket-pipelines.yml 1`] = ` -" -definitions: +"definitions: steps: - step: &build-step name: SonarQube analysis diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx index 562ef915d5e..12c20815c99 100644 --- a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx @@ -30,6 +30,7 @@ import { AlmKeys } from '../../../../types/alm-settings'; import { Feature } from '../../../../types/features'; import { getCommonNodes, + getCopyToClipboardHostURLValue, getCopyToClipboardValue, getTutorialActionButtons, getTutorialBuildButtons, @@ -62,90 +63,106 @@ it('should follow and complete all steps', async () => { expect(await ui.secretsStepTitle.find()).toBeInTheDocument(); // Env variables step - expect(getCopyToClipboardValue(0, 'Copy to clipboard')).toMatchSnapshot('sonar token key'); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toMatchSnapshot('sonarqube host url key'); - expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot( + expect( + getCopyToClipboardValue({ i: 0, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot('sonar token key'); + expect( + getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard', inlineSnippet: true }), + ).toMatchSnapshot('sonarqube host url key'); + expect(getCopyToClipboardHostURLValue({ i: 2, name: 'Copy to clipboard' })).toMatchSnapshot( 'sonarqube host url value', ); // Create/update configuration file step // Maven await user.click(ui.mavenBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Maven: .github/workflows/build.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Maven: .github/workflows/build.yml', + ); // Gradle await user.click(ui.gradleBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Groovy: build.gradle'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('Groovy: build.gradle'); await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Kotlin: build.gradle.kts'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Gradle: .github/workflows/build.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Kotlin: build.gradle.kts', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Gradle: .github/workflows/build.yml', + ); // .NET await user.click(ui.dotnetBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('.NET: .github/workflows/build.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + '.NET: .github/workflows/build.yml', + ); // Cpp await user.click(ui.cppBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: .github/workflows/build.yml', ); await user.click(ui.autoConfigManual.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('C++: sonar-project.properties'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'C++: sonar-project.properties', + ); await user.click(ui.linuxButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ Linux: .github/workflows/build.yml', ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ Linux arm64: .github/workflows/build.yml', ); await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ Windows: .github/workflows/build.yml', ); await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ MacOS: .github/workflows/build.yml', ); // Objective-C await user.click(ui.objCBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'Objective-C: sonar-project.properties', ); await user.click(ui.linuxButton.get()); await user.click(ui.x86_64Button.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'Objective-C Linux: .github/workflows/build.yml', ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'Objective-C Linux arm64: .github/workflows/build.yml', ); await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'Objective-C Windows: .github/workflows/build.yml', ); await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'Objective-C MacOS: .github/workflows/build.yml', ); // Dart await user.click(ui.dartBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart: .github/workflows/build.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart: .github/workflows/build.yml', + ); // Other await user.click(ui.otherBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: sonar-project.properties', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'C++ (automatic) and other: .github/workflows/build.yml', ); @@ -161,7 +178,7 @@ it('should generate/delete a new token or use existing one', async () => { // Generate token await user.click(ui.genTokenDialogButton.get()); await user.click(ui.generateTokenButton.get()); - expect(getCopyToClipboardValue()).toEqual('generatedtoken2'); + expect(getCopyToClipboardValue({ inlineSnippet: true })).toEqual('generatedtoken2'); // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/__snapshots__/GithubActionTutorial-it.tsx.snap b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/__snapshots__/GithubActionTutorial-it.tsx.snap index bc5a71eb7fc..e3cd4f02c53 100644 --- a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/__snapshots__/GithubActionTutorial-it.tsx.snap +++ b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/__snapshots__/GithubActionTutorial-it.tsx.snap @@ -1,35 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should follow and complete all steps: Dart: .github/workflows/build.yml 1`] = ` -" -name: Build - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] - -jobs: - build: - name: Build and analyze - runs-on: ubuntu-latest - steps: - - <commands to build your project> - - name: Download sonar-scanner - run: | - curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-6.1.0.4477-linux-x64.zip - unzip $HOME/.sonar/sonar-scanner.zip -o -d $HOME/.sonar/ - - name: Run sonar-scanner - env: - SONAR_TOKEN: \${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: \${{ secrets.SONAR_HOST_URL }} - run: | - sonar-scanner-6.1.0.4477-linux-x64/bin/sonar-scanner \\ - -Dsonar.projectKey=my-project" -`; - exports[`should follow and complete all steps: .NET: .github/workflows/build.yml 1`] = ` "name: Build @@ -297,6 +267,35 @@ jobs: exports[`should follow and complete all steps: C++: sonar-project.properties 1`] = `"sonar.projectKey=my-project"`; +exports[`should follow and complete all steps: Dart: .github/workflows/build.yml 1`] = ` +"name: Build + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - <commands to build your project> + - name: Download sonar-scanner + run: | + curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-6.1.0.4477-linux-x64.zip + unzip $HOME/.sonar/sonar-scanner.zip -o -d $HOME/.sonar/ + - name: Run sonar-scanner + env: + SONAR_TOKEN: \${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: \${{ secrets.SONAR_HOST_URL }} + run: | + sonar-scanner-6.1.0.4477-linux-x64/bin/sonar-scanner \\ + -Dsonar.projectKey=my-project" +`; + exports[`should follow and complete all steps: Gradle: .github/workflows/build.yml 1`] = ` "name: Build diff --git a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx index 26fec42cb08..93c062e5fa6 100644 --- a/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-it.tsx @@ -26,6 +26,7 @@ import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUt import { byRole } from '../../../../sonar-aligned/helpers/testSelector'; import { getCommonNodes, + getCopyToClipboardHostURLValue, getCopyToClipboardValue, getTutorialActionButtons, getTutorialBuildButtons, @@ -56,47 +57,61 @@ it('should follow and complete all steps', async () => { expect(await ui.secretsStepTitle.find()).toBeInTheDocument(); // Env variables step - expect(getCopyToClipboardValue(0, 'Copy to clipboard')).toMatchSnapshot('sonar token key'); - expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toMatchSnapshot('sonarqube host url key'); - expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy to clipboard' })).toMatchSnapshot( + 'sonar token key', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy to clipboard' })).toMatchSnapshot( + 'sonarqube host url key', + ); + expect(getCopyToClipboardHostURLValue({ i: 2, name: 'Copy to clipboard' })).toMatchSnapshot( 'sonarqube host url value', ); // Create/update configuration file step // Maven await user.click(ui.mavenBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Maven: pom.xml'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Maven: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('Maven: pom.xml'); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot('Maven: gitlab-ci.yml'); // Gradle await user.click(ui.gradleBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Groovy: build.gradle'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('Groovy: build.gradle'); await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Kotlin: build.gradle.kts'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Gradle: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Kotlin: build.gradle.kts', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot('Gradle: gitlab-ci.yml'); // .NET await user.click(ui.dotnetBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('.NET: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('.NET: gitlab-ci.yml'); // C++/Objective-C await user.click(ui.cppBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('CPP: sonar-project.properties'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('CPP: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'CPP: sonar-project.properties', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot('CPP: gitlab-ci.yml'); // c++ manual config await user.click(ui.autoConfigManual.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('CPP - manual: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'CPP - manual: gitlab-ci.yml', + ); // Dart await user.click(ui.dartBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart: sonar-project.properties'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Dart: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart: sonar-project.properties', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot('Dart: gitlab-ci.yml'); // Other await user.click(ui.otherBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Other: sonar-project.properties'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Other: gitlab-ci.yml'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Other: sonar-project.properties', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot('Other: gitlab-ci.yml'); expect(ui.allSetSentence.get()).toBeInTheDocument(); }); @@ -110,7 +125,7 @@ it('should generate/delete a new token or use existing one', async () => { // Generate token await user.click(ui.genTokenDialogButton.get()); await user.click(ui.generateTokenButton.get()); - expect(getCopyToClipboardValue()).toEqual('generatedtoken2'); + expect(getCopyToClipboardValue({ inlineSnippet: true })).toEqual('generatedtoken2'); // Revoke current token and create new one await user.click(ui.deleteTokenButton.get()); diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx index 61ccedae4bd..00da86bece4 100644 --- a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx @@ -99,101 +99,121 @@ it.each([AlmKeys.BitbucketCloud, AlmKeys.BitbucketServer, AlmKeys.GitHub, AlmKey // 3. Multibranch Pipeline Job expect(ui.multiBranchPipelineSecondListItem(alm).get()).toBeInTheDocument(); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(`ref spec`); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot(`ref spec`); // 4. Create DevOps platform webhook expect(ui.webhookStepTitle(alm).get()).toBeInTheDocument(); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot(`jenkins url`); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot(`jenkins url`); // 5. Create jenkinsfile // Maven await user.click(ui.mavenBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`maven jenkinsfile`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot(`maven jenkinsfile`); // Gradle (Groovy) await user.click(ui.gradleBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`Groovy: build.gradle file`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `Groovy: build.gradle file`, + ); // Gradle(Kotlin) await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`Kotlin: build.gradle.kts file`); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot(`gradle jenkinsfile`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `Kotlin: build.gradle.kts file`, + ); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot(`gradle jenkinsfile`); // .NET await user.click(ui.dotnetBuildButton.get()); await user.click(ui.windowsDotnetCoreButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`windows dotnet core jenkinsfile`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `windows dotnet core jenkinsfile`, + ); await user.click(ui.windowsDotnetFrameworkButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( `windows dotnet framework jenkinsfile`, ); await user.click(ui.linuxDotnetCoreButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`linux dotnet core jenkinsfile`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `linux dotnet core jenkinsfile`, + ); // C++ (automatic) await user.click(ui.cppBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( `c++ (automatic and other): build tools sonar-project.properties code`, ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (automatic and other): build tools jenkinsfile`, ); // C++ (manual) await user.click(ui.autoConfigManual.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`sonar-project.properties code`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `sonar-project.properties code`, + ); await user.click(ui.linuxButton.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (manual) and objectivec: linux jenkinsfile`, ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (manual) and objectivec: linux arm64 jenkinsfile`, ); await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (manual) and objectivec: windows jenkinsfile`, ); await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (manual) and objectivec: macos jenkinsfile`, ); // Objective-C await user.click(ui.objCBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`sonar-project.properties code`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `sonar-project.properties code`, + ); await user.click(ui.linuxButton.get()); await user.click(ui.x86_64Button.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot(`objectivec: linux jenkinsfile`); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + `objectivec: linux jenkinsfile`, + ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `objectivec: linux arm64 jenkinsfile`, ); await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot(`objectivec: windows jenkinsfile`); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + `objectivec: windows jenkinsfile`, + ); await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot(`objectivec: macos jenkinsfile`); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + `objectivec: macos jenkinsfile`, + ); // Dart await user.click(ui.dartBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot(`Dart: sonar-project.properties`); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot(`Dart: jenkinsfile`); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + `Dart: sonar-project.properties`, + ); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot(`Dart: jenkinsfile`); // Other await user.click(ui.otherBuildButton.get()); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( `c++ (automatic and other): build tools sonar-project.properties code`, ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( `c++ (automatic and other): build tools jenkinsfile`, ); diff --git a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx index 905eb73b915..b6f15c19ae8 100644 --- a/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx @@ -35,6 +35,10 @@ jest.mock('../../../../api/settings', () => ({ getAllValues: jest.fn().mockResolvedValue([]), })); +jest.mock('clipboard', () => ({ + copy: jest.fn(), +})); + const tokenMock = new UserTokensMock(); afterEach(() => { @@ -100,50 +104,66 @@ it('can choose build tools and copy provided settings', async () => { // Maven await user.click(ui.mavenBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('maven: execute scanner'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot('maven: execute scanner'); // Gradle await user.click(ui.gradleBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('gradle: sonarqube plugin'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('gradle: execute scanner'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'gradle: sonarqube plugin', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'gradle: execute scanner', + ); // Dotnet - Core await user.click(ui.dotnetBuildButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'dotnet core: install scanner globally', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('dotnet core: execute command 1'); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot('dotnet core: execute command 2'); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('dotnet core: execute command 3'); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'dotnet core: execute command 1', + ); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + 'dotnet core: execute command 2', + ); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + 'dotnet core: execute command 3', + ); // Dotnet - Framework await user.click(ui.dotnetFrameworkButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('dotnet framework: execute command 1'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('dotnet framework: execute command 2'); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot('dotnet framework: execute command 3'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'dotnet framework: execute command 1', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'dotnet framework: execute command 2', + ); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( + 'dotnet framework: execute command 3', + ); // C++ - Automatic await user.click(ui.cppBuildButton.get()); await user.click(ui.linuxButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other linux: download scanner', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other linux: execute scanner', ); await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other linux arm64: download scanner', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other linux arm64: execute scanner', ); await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other windows: execute scanner', ); await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other macos: execute scanner', ); @@ -151,146 +171,170 @@ it('can choose build tools and copy provided settings', async () => { await user.click(ui.autoConfigManual.get()); await user.click(ui.linuxButton.get()); await user.click(ui.x86_64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('c++ (manual) linux: execute scanner'); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + 'c++ (manual) linux: execute scanner', + ); // C++ - Linux (ARM64) await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux arm64: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux arm64: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux arm64: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) linux arm64: execute scanner', ); // C++ - Windows await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) windows: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) windows: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) windows: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) windows: execute scanner', ); // C++ - MacOS await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) macos: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) macos: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'c++ (manual) macos: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('c++ (manual) macos: execute scanner'); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + 'c++ (manual) macos: execute scanner', + ); // Objective-C - Linux (x86_64) await user.click(ui.objCBuildButton.get()); await user.click(ui.linuxButton.get()); await user.click(ui.x86_64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'objective-c linux: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('objective-c linux: download scanner'); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'objective-c linux: download scanner', + ); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'objective-c linux: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('objective-c linux: execute scanner'); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + 'objective-c linux: execute scanner', + ); // Objective-C - Linux (ARM64) await user.click(ui.arm64Button.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'objective-c linux arm64: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'objective-c linux arm64: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'objective-c linux arm64: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( 'objective-c linux arm64: execute scanner', ); // Objective-C - Windows await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'objective-c windows: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( 'objective-c windows: download scanner', ); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'objective-c windows: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( 'objective-c windows: execute scanner', ); // Objective-C - MacOS await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'objective-c macos: download build wrapper', ); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('objective-c macos: download scanner'); - expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'objective-c macos: download scanner', + ); + expect(getCopyToClipboardValue({ i: 2, name: 'Copy' })).toMatchSnapshot( 'objective-c macos: execute build wrapper', ); - expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('objective-c macos: execute scanner'); + expect(getCopyToClipboardValue({ i: 3, name: 'Copy' })).toMatchSnapshot( + 'objective-c macos: execute scanner', + ); // Dart - Linux await user.click(ui.dartBuildButton.get()); await user.click(ui.linuxButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart linux: download scanner'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Dart linux: execute scanner'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart linux: download scanner', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Dart linux: execute scanner', + ); // Dart - Windows await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart windows: download scanner'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Dart windows: execute scanner'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart windows: download scanner', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Dart windows: execute scanner', + ); // Dart - MacOS await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Dart macos: download scanner'); - expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Dart macos: execute scanner'); + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( + 'Dart macos: download scanner', + ); + expect(getCopyToClipboardValue({ i: 1, name: 'Copy' })).toMatchSnapshot( + 'Dart macos: execute scanner', + ); // Other - Linux await user.click(ui.otherBuildButton.get()); await user.click(ui.linuxButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other linux: execute scanner', ); // Other - Windows await user.click(ui.windowsButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other windows: execute scanner', ); // Other - MacOS await user.click(ui.macosButton.get()); - expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot( + expect(getCopyToClipboardValue({ i: 0, name: 'Copy' })).toMatchSnapshot( 'c++ (automatic) and other macos: execute scanner', ); }); diff --git a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts index c2f727cd252..2aff448489b 100644 --- a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts +++ b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts @@ -27,8 +27,30 @@ const CI_TRANSLATE_MAP: Partial<Record<TutorialModes, string>> = { [TutorialModes.GitLabCI]: 'gitlab_ci', }; -export function getCopyToClipboardValue(i = 0, name = 'copy_to_clipboard') { - return screen.getAllByRole('button', { name })[i].getAttribute('data-clipboard-text'); +interface GetCopyToClipboardValueArgs { + i?: number; + inlineSnippet?: boolean; + name?: string; +} + +export function getCopyToClipboardValue({ + i = 0, + inlineSnippet = false, + name = 'copy_to_clipboard', +}: GetCopyToClipboardValueArgs = {}) { + const button = screen.getAllByRole('button', { name })[i]; + + return inlineSnippet + ? button.previousSibling?.firstChild?.textContent + : button.nextSibling?.firstChild?.textContent; +} + +export function getCopyToClipboardHostURLValue({ + i = 0, + name = 'copy_to_clipboard', +}: Omit<GetCopyToClipboardValueArgs, 'inlineSnippet'> = {}) { + return screen.getAllByRole('button', { name })[i].nextSibling?.nextSibling?.firstChild + ?.textContent; } export function getCommonNodes(ci: TutorialModes) { |