diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index ff7f87b661a..45189667e5d 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -14,10 +14,10 @@ "@octokit/plugin-retry": "^7.2.0", "@octokit/plugin-throttling": "^9.6.0", "@octokit/rest": "^21.1.1", + "@vscode-elements/react-elements": "^0.9.0", "@vscode/codicons": "^0.0.36", "@vscode/debugadapter": "^1.59.0", "@vscode/debugprotocol": "^1.68.0", - "@vscode/webview-ui-toolkit": "^1.0.1", "ajv": "^8.11.0", "chokidar": "^3.6.0", "d3": "^7.9.0", @@ -5052,6 +5052,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", + "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/react": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.7.tgz", + "integrity": "sha512-cencnwwLXQKiKxjfFzSgZRngcWJzUDZi/04E0fSaF86wZgchMdvTyu+lE36DrUfvuus3bH8+xLPrhM1cTjwpzw==", + "license": "BSD-3-Clause", + "peerDependencies": { + "@types/react": "17 || 18 || 19" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@mdx-js/react": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", @@ -5091,47 +5115,6 @@ "node": ">= 14" } }, - "node_modules/@microsoft/fast-element": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-1.12.0.tgz", - "integrity": "sha512-gQutuDHPKNxUEcQ4pypZT4Wmrbapus+P9s3bR/SEOLsMbNqNoXigGImITygI5zhb+aA5rzflM6O8YWkmRbGkPA==" - }, - "node_modules/@microsoft/fast-foundation": { - "version": "2.49.4", - "resolved": "https://registry.npmjs.org/@microsoft/fast-foundation/-/fast-foundation-2.49.4.tgz", - "integrity": "sha512-5I2tSPo6bnOfVAIX7XzX+LhilahwvD7h+yzl3jW0t5IYmMX9Lci9VUVyx5f8hHdb1O9a8Y9Atb7Asw7yFO/u+w==", - "dependencies": { - "@microsoft/fast-element": "^1.12.0", - "@microsoft/fast-web-utilities": "^5.4.1", - "tabbable": "^5.2.0", - "tslib": "^1.13.0" - } - }, - "node_modules/@microsoft/fast-foundation/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@microsoft/fast-react-wrapper": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@microsoft/fast-react-wrapper/-/fast-react-wrapper-0.3.22.tgz", - "integrity": "sha512-XhlX4m6znh7XW92oPvlKoG9USUn9JtF9rP1qtUoIbkaDaFtUS+H8o1Jn6/oK/rS44LbBLJXrvRkInmSWlDiGFw==", - "dependencies": { - "@microsoft/fast-element": "^1.12.0", - "@microsoft/fast-foundation": "^2.49.4" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, - "node_modules/@microsoft/fast-web-utilities": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@microsoft/fast-web-utilities/-/fast-web-utilities-5.4.1.tgz", - "integrity": "sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==", - "dependencies": { - "exenv-es6": "^1.1.1" - } - }, "node_modules/@mswjs/interceptors": { "version": "0.37.3", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.3.tgz", @@ -7918,6 +7901,12 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@types/undertaker": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.11.tgz", @@ -8878,6 +8867,28 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vscode-elements/elements": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@vscode-elements/elements/-/elements-1.14.0.tgz", + "integrity": "sha512-fUOP8O/Pwy8zbD8hGSy1plBg/764hdM9jIMu8uG7GQJOrOB+uQ/ystYxkiUcN6P7OBHvqkBKO1j6vDrkaOJg6Q==", + "license": "MIT", + "dependencies": { + "lit": "^3.2.1" + } + }, + "node_modules/@vscode-elements/react-elements": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@vscode-elements/react-elements/-/react-elements-0.9.0.tgz", + "integrity": "sha512-pGWp6OBDAZXJ0tZqN+2SCiKhvhW3/cE4XJyiVHXH4Ft6KteuNVg20oexFv0M66U9iAZElQjPF8M9pBBABLaUZg==", + "license": "ISC", + "dependencies": { + "@lit/react": "^1.0.6", + "@vscode-elements/elements": "^1.13.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@vscode/codicons": { "version": "0.0.36", "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", @@ -9106,20 +9117,6 @@ "win32" ] }, - "node_modules/@vscode/webview-ui-toolkit": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vscode/webview-ui-toolkit/-/webview-ui-toolkit-1.4.0.tgz", - "integrity": "sha512-modXVHQkZLsxgmd5yoP3ptRC/G8NBDD+ob+ngPiWNQdlrH6H1xR/qgOBD85bfU3BhOB5sZzFWBwwhp9/SfoHww==", - "dependencies": { - "@microsoft/fast-element": "^1.12.0", - "@microsoft/fast-foundation": "^2.49.4", - "@microsoft/fast-react-wrapper": "^0.3.22", - "tslib": "^2.6.2" - }, - "peerDependencies": { - "react": ">=16.9.0" - } - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -13788,11 +13785,6 @@ "dev": true, "license": "ISC" }, - "node_modules/exenv-es6": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exenv-es6/-/exenv-es6-1.1.1.tgz", - "integrity": "sha512-vlVu3N8d6yEMpMsEm+7sUBAI81aqYYuEvfK0jNqmdb/OPXzzH7QWDDnVjMvDSY47JdHEqx/dfC/q8WkfoTmpGQ==" - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -20488,6 +20480,37 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/lit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", + "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.1.0", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-element": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.2.0" + } + }, + "node_modules/lit-html": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -25329,11 +25352,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/tabbable": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index a375dee2b7f..72f506aa68f 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1972,10 +1972,10 @@ "@octokit/plugin-retry": "^7.2.0", "@octokit/plugin-throttling": "^9.6.0", "@octokit/rest": "^21.1.1", + "@vscode-elements/react-elements": "^0.9.0", "@vscode/codicons": "^0.0.36", "@vscode/debugadapter": "^1.59.0", "@vscode/debugprotocol": "^1.68.0", - "@vscode/webview-ui-toolkit": "^1.0.1", "ajv": "^8.11.0", "chokidar": "^3.6.0", "d3": "^7.9.0", diff --git a/extensions/ql-vscode/src/stories/Overview.mdx b/extensions/ql-vscode/src/stories/Overview.mdx index cc06ee5cfca..7d9939cb4d9 100644 --- a/extensions/ql-vscode/src/stories/Overview.mdx +++ b/extensions/ql-vscode/src/stories/Overview.mdx @@ -1,7 +1,5 @@ import { Canvas, Meta, Story } from '@storybook/blocks'; -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react'; - import iframeImage from './images/update-css-variables-iframe.png'; import stylesImage from './images/update-css-variables-styles.png'; import bodyImage from './images/update-css-variables-body.png'; diff --git a/extensions/ql-vscode/src/stories/common/Alert.stories.tsx b/extensions/ql-vscode/src/stories/common/Alert.stories.tsx index 1a5db4f14f6..2267ccb2696 100644 --- a/extensions/ql-vscode/src/stories/common/Alert.stories.tsx +++ b/extensions/ql-vscode/src/stories/common/Alert.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryFn } from "@storybook/react"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; +import { VscodeButton } from "@vscode-elements/react-elements"; import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer"; import { Alert } from "../../view/common"; @@ -84,8 +84,8 @@ ErrorWithButtons.args = { "Request to https://api.github.com/repos/octodemo/Hello-World/code-scanning/codeql/queries failed. Try running this query again.", actions: ( <> - View actions logs - Retry + View actions logs + Retry > ), }; diff --git a/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx index 423a948cd0e..16cb303b786 100644 --- a/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx @@ -220,6 +220,7 @@ LibraryRow.args = { ], }, modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]), + selectedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]), viewState: createMockModelEditorViewState({ showGenerateButton: true, }), diff --git a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx index 6b10353d0a9..f7bf0748b20 100644 --- a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx @@ -112,6 +112,7 @@ Source.args = { modeledMethods: [{ ...modeledMethod, type: "source" }], methodCanBeModeled: true, viewState, + onChange: () => {}, }; export const Sink = Template.bind({}); @@ -120,6 +121,7 @@ Sink.args = { modeledMethods: [{ ...modeledMethod, type: "sink" }], methodCanBeModeled: true, viewState, + onChange: () => {}, }; export const Summary = Template.bind({}); @@ -136,6 +138,7 @@ Neutral.args = { modeledMethods: [{ ...modeledMethod, type: "neutral" }], methodCanBeModeled: true, viewState, + onChange: () => {}, }; export const AlreadyModeled = Template.bind({}); @@ -155,6 +158,7 @@ MultipleModelings.args = { ], methodCanBeModeled: true, viewState, + onChange: () => {}, }; export const ValidationError = Template.bind({}); @@ -166,6 +170,7 @@ ValidationError.args = { ], methodCanBeModeled: true, viewState, + onChange: () => {}, }; export const MultipleValidationErrors = Template.bind({}); @@ -180,4 +185,5 @@ MultipleValidationErrors.args = { ], methodCanBeModeled: true, viewState, + onChange: () => {}, }; diff --git a/extensions/ql-vscode/src/stories/results/AlertTable.stories.tsx b/extensions/ql-vscode/src/stories/results/AlertTable.stories.tsx index bfb179a1fed..d2cc19f3916 100644 --- a/extensions/ql-vscode/src/stories/results/AlertTable.stories.tsx +++ b/extensions/ql-vscode/src/stories/results/AlertTable.stories.tsx @@ -431,4 +431,7 @@ WithCodeFlows.args = { showRawResults={() => action("show-raw-results")} /> ), + userSettings: { + shouldShowProvenance: true, + }, }; diff --git a/extensions/ql-vscode/src/stories/types.d.ts b/extensions/ql-vscode/src/stories/types.d.ts new file mode 100644 index 00000000000..1eabbb4297e --- /dev/null +++ b/extensions/ql-vscode/src/stories/types.d.ts @@ -0,0 +1 @@ +declare module "*.module.css"; diff --git a/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.module.css b/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.module.css new file mode 100644 index 00000000000..70a8e8a1577 --- /dev/null +++ b/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.module.css @@ -0,0 +1,56 @@ +/* Styles have been copied from https://vscode-elements.github.io/elements-lite/components/action-button/configurator/ */ +.actionButton { + align-items: center; + background-color: transparent; + border-color: transparent; + border-style: solid; + border-width: 1px; + border-radius: 5px; + color: var(--vscode-foreground); + display: inline-flex; + cursor: pointer; + padding: 0; + user-select: none; +} + +.actionButton:disabled { + color: var(--vscode-disabledForeground); + cursor: default; + pointer-events: none; +} + +.actionButton :global(.codicon), +.actionButton svg { + color: var(--vscode-icon-foreground); + display: block; + padding: 2px; +} + +.actionButton svg { + box-sizing: content-box; + height: 16px; + width: 16px; +} + +.actionButton:disabled :global(.codicon), +.actionButton:disabled svg { + color: var(--vscode-disabledForeground); +} + +.actionButton:hover { + background-color: var(--vscode-toolbar-hoverBackground); + outline: 1px dotted var(--vscode-contrastActiveBorder, transparent); + outline-offset: -1px; +} + +.actionButton:active { + background-color: var(--vscode-toolbar-activeBackground); +} + +.actionButton:focus { + outline: none; +} + +.actionButton:focus-visible { + border-color: var(--vscode-focusBorder); +} diff --git a/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.tsx b/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.tsx new file mode 100644 index 00000000000..f673fd56f96 --- /dev/null +++ b/extensions/ql-vscode/src/view/common/ActionButton/ActionButton.tsx @@ -0,0 +1,9 @@ +import styles from "./ActionButton.module.css"; + +// This is needed because vscode-elements/elements does not implement +// the same styles for icon buttons as vscode/webview-ui-toolkit +export const ActionButton = (props: React.ComponentProps<"button">) => ( + + {props.children} + +); diff --git a/extensions/ql-vscode/src/view/common/Badge.tsx b/extensions/ql-vscode/src/view/common/Badge.tsx new file mode 100644 index 00000000000..cb9826bcab8 --- /dev/null +++ b/extensions/ql-vscode/src/view/common/Badge.tsx @@ -0,0 +1,8 @@ +import { VscodeBadge } from "@vscode-elements/react-elements"; + +// This applies the counter variant by default so the border-radius attribute is set +export const Badge = (props: React.ComponentProps) => ( + + {props.children} + +); diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx index 277013084f1..cc232060f0a 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx @@ -1,6 +1,9 @@ import type { ChangeEvent, SetStateAction } from "react"; import { useCallback } from "react"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { + VscodeOption, + VscodeSingleSelect, +} from "@vscode-elements/react-elements"; import type { CodeFlow } from "../../../variant-analysis/shared/analysis-result"; @@ -35,12 +38,12 @@ export const CodeFlowsDropdown = ({ .toString(); return ( - + {codeFlows.map((codeFlow, index) => ( - + {getCodeFlowName(codeFlow)} - + ))} - + ); }; diff --git a/extensions/ql-vscode/src/view/common/Link.tsx b/extensions/ql-vscode/src/view/common/Link.tsx index 9fa73906433..a0e37b92bcd 100644 --- a/extensions/ql-vscode/src/view/common/Link.tsx +++ b/extensions/ql-vscode/src/view/common/Link.tsx @@ -3,12 +3,12 @@ import { styled } from "styled-components"; export const Link = styled.a` background: transparent; box-sizing: border-box; - color: var(--link-foreground); + color: var(--vscode-textLink-foreground); cursor: pointer; fill: currentcolor; - font-family: var(--font-family); - font-size: var(--type-ramp-base-font-size); - line-height: var(--type-ramp-base-line-height); + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + line-height: normal; outline: none; &:hover { diff --git a/extensions/ql-vscode/src/view/common/SearchBox.tsx b/extensions/ql-vscode/src/view/common/SearchBox.tsx index 464d303fa7e..7837c143e76 100644 --- a/extensions/ql-vscode/src/view/common/SearchBox.tsx +++ b/extensions/ql-vscode/src/view/common/SearchBox.tsx @@ -1,12 +1,16 @@ import { useCallback } from "react"; import { styled } from "styled-components"; -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; +import { VscodeTextfield } from "@vscode-elements/react-elements"; import { Codicon } from "./icon"; -const TextField = styled(VSCodeTextField)` +const TextField = styled(VscodeTextfield)` width: 100%; `; +const SearchIcon = styled(Codicon)` + margin: 0 8px; +`; + type Props = { value: string; placeholder: string; @@ -37,7 +41,7 @@ export const SearchBox = ({ onInput={handleInput} className={className} > - + ); }; diff --git a/extensions/ql-vscode/src/view/common/SuggestBox/SuggestBox.tsx b/extensions/ql-vscode/src/view/common/SuggestBox/SuggestBox.tsx index 32240fac67e..7e190a6d05a 100644 --- a/extensions/ql-vscode/src/view/common/SuggestBox/SuggestBox.tsx +++ b/extensions/ql-vscode/src/view/common/SuggestBox/SuggestBox.tsx @@ -14,15 +14,15 @@ import { useRole, } from "@floating-ui/react"; import { css, styled } from "styled-components"; -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import type { Option } from "./options"; import { findMatchingOptions } from "./options"; import { SuggestBoxItem } from "./SuggestBoxItem"; import { LabelText } from "./LabelText"; import type { Diagnostic } from "./diagnostics"; import { useOpenKey } from "./useOpenKey"; +import { VscodeTextfield } from "@vscode-elements/react-elements"; -const Input = styled(VSCodeTextField)<{ $error: boolean }>` +const Input = styled(VscodeTextfield)<{ $error: boolean }>` width: 100%; font-family: var(--vscode-editor-font-family); @@ -96,7 +96,7 @@ export type SuggestBoxProps< /** * Can be used to render a different component for the input. This is used - * in testing to use default HTML components rather than the VSCodeTextField + * in testing to use default HTML components rather than the VscodeTextfield * for easier testing. * @param props The props returned by `getReferenceProps` of {@link useInteractions} */ diff --git a/extensions/ql-vscode/src/view/compare-performance/RenamingInput.tsx b/extensions/ql-vscode/src/view/compare-performance/RenamingInput.tsx index 6d86c7e8182..c938cc6d1a4 100644 --- a/extensions/ql-vscode/src/view/compare-performance/RenamingInput.tsx +++ b/extensions/ql-vscode/src/view/compare-performance/RenamingInput.tsx @@ -1,9 +1,6 @@ import type { ChangeEvent } from "react"; import { styled } from "styled-components"; -import { - VSCodeButton, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react"; +import { VscodeButton, VscodeTextfield } from "@vscode-elements/react-elements"; import { Codicon } from "../common"; export class Renaming { @@ -25,7 +22,7 @@ function tryCompilePattern(pattern: string): RegExp | undefined { } } -const Input = styled(VSCodeTextField)` +const Input = styled(VscodeTextfield)` width: 20em; `; @@ -86,21 +83,21 @@ export function RenamingInput(props: RenamingInputProps) { setRenamings(newRenamings); }} > - setRenamings(renamings.filter((_, i) => i !== index)) } > - + ))} - setRenamings([...renamings, new Renaming("", "")])} > Add renaming rule - + ); } diff --git a/extensions/ql-vscode/src/view/jest.setup.ts b/extensions/ql-vscode/src/view/jest.setup.ts index 23cb3e2f973..31dc6657d40 100644 --- a/extensions/ql-vscode/src/view/jest.setup.ts +++ b/extensions/ql-vscode/src/view/jest.setup.ts @@ -18,6 +18,20 @@ Object.defineProperty(window, "matchMedia", { // Used by Primer React window.CSS.supports = jest.fn().mockResolvedValue(false); +// Functions that are not implemented in jsdom +window.CSSStyleSheet.prototype.replaceSync = jest + .fn() + .mockReturnValue(undefined); +window.ElementInternals.prototype.setFormValue = jest + .fn() + .mockReturnValue(undefined); +window.ElementInternals.prototype.setValidity = jest + .fn() + .mockReturnValue(undefined); +window.HTMLSlotElement.prototype.assignedElements = jest + .fn() + .mockReturnValue([]); + // Store this on the window so we can mock it window.vsCodeApi = { postMessage: jest.fn(), diff --git a/extensions/ql-vscode/src/view/method-modeling/MultipleModeledMethodsPanel.tsx b/extensions/ql-vscode/src/view/method-modeling/MultipleModeledMethodsPanel.tsx index bb2fd6b2ab5..ed55c497d71 100644 --- a/extensions/ql-vscode/src/view/method-modeling/MultipleModeledMethodsPanel.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/MultipleModeledMethodsPanel.tsx @@ -7,7 +7,6 @@ import { } from "../../model-editor/shared/multiple-modeled-methods"; import { styled } from "styled-components"; import { MethodModelingInputs } from "./MethodModelingInputs"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { Codicon } from "../common"; import { validateModeledMethods } from "../../model-editor/shared/validation"; import { ModeledMethodAlert } from "./ModeledMethodAlert"; @@ -15,6 +14,7 @@ import type { QueryLanguage } from "../../common/query-language"; import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty"; import { sendTelemetry } from "../common/telemetry"; import type { ModelConfig } from "../../model-editor/languages"; +import { ActionButton } from "../common/ActionButton/ActionButton"; export type MultipleModeledMethodsPanelProps = { language: QueryLanguage; @@ -168,21 +168,19 @@ export const MultipleModeledMethodsPanel = ({ )} diff --git a/extensions/ql-vscode/src/view/method-modeling/__tests__/MultipleModeledMethodsPanel.spec.tsx b/extensions/ql-vscode/src/view/method-modeling/__tests__/MultipleModeledMethodsPanel.spec.tsx index 9c0e0b464da..27372287bfc 100644 --- a/extensions/ql-vscode/src/view/method-modeling/__tests__/MultipleModeledMethodsPanel.spec.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/__tests__/MultipleModeledMethodsPanel.spec.tsx @@ -52,32 +52,26 @@ describe(MultipleModeledMethodsPanel.name, () => { ).toHaveValue("none"); }); - it("disables all pagination", () => { + it("disables all pagination", async () => { render(); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeDisabled(); + const prevButton = await screen.findByLabelText("Previous modeling"); + const nextButton = await screen.findByLabelText("Next modeling"); + + expect(prevButton).toBeDisabled(); + expect(nextButton).toBeDisabled(); expect(screen.queryByText("0/0")).not.toBeInTheDocument(); expect(screen.queryByText("1/0")).not.toBeInTheDocument(); }); - it("cannot add or delete modeling", () => { + it("cannot add or delete modeling", async () => { render(); - expect( - screen - .getByLabelText("Delete modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); - expect( - screen.getByLabelText("Add modeling").getElementsByTagName("input")[0], - ).toBeDisabled(); + const deleteButton = await screen.findByLabelText("Delete modeling"); + const addButton = await screen.findByLabelText("Add modeling"); + + expect(deleteButton).toBeDisabled(); + expect(addButton).toBeDisabled(); }); }); @@ -104,28 +98,22 @@ describe(MultipleModeledMethodsPanel.name, () => { ).toHaveValue("sink"); }); - it("disables all pagination", () => { + it("disables all pagination", async () => { render(); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeDisabled(); + const prevButton = await screen.findByLabelText("Previous modeling"); + const nextButton = await screen.findByLabelText("Next modeling"); + + expect(prevButton).toBeDisabled(); + expect(nextButton).toBeDisabled(); expect(screen.queryByText("1/1")).not.toBeInTheDocument(); }); - it("cannot delete modeling", () => { + it("cannot delete modeling", async () => { render(); - expect( - screen - .getByLabelText("Delete modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); + const deleteButton = await screen.findByLabelText("Delete modeling"); + expect(deleteButton).toBeDisabled(); }); it("can add modeling", async () => { @@ -199,37 +187,26 @@ describe(MultipleModeledMethodsPanel.name, () => { it("disables the correct pagination", async () => { render(); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeEnabled(); + const prevButton = await screen.findByLabelText("Previous modeling"); + const nextButton = await screen.findByLabelText("Next modeling"); + + expect(prevButton).toBeDisabled(); + expect(nextButton).toBeEnabled(); }); it("can use the pagination", async () => { render(); - await userEvent.click(screen.getByLabelText("Next modeling")); + const prevButton = await screen.findByLabelText("Previous modeling"); + const nextButton = await screen.findByLabelText("Next modeling"); + await userEvent.click(nextButton); await waitFor(() => { - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(prevButton).toBeEnabled(); }); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeDisabled(); + expect(prevButton).toBeEnabled(); + expect(nextButton).toBeDisabled(); expect(screen.getByText("2/2")).toBeInTheDocument(); expect( @@ -445,34 +422,20 @@ describe(MultipleModeledMethodsPanel.name, () => { it("can use the pagination", async () => { render(); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeDisabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeEnabled(); + const prevButton = await screen.findByLabelText("Previous modeling"); + const nextButton = await screen.findByLabelText("Next modeling"); + expect(prevButton).toBeDisabled(); + expect(nextButton).toBeEnabled(); expect(screen.getByText("1/3")).toBeInTheDocument(); - await userEvent.click(screen.getByLabelText("Next modeling")); + await userEvent.click(nextButton); await waitFor(() => { - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(prevButton).toBeEnabled(); }); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(prevButton).toBeEnabled(); + expect(nextButton).toBeEnabled(); expect(screen.getByText("2/3")).toBeInTheDocument(); expect( @@ -481,16 +444,10 @@ describe(MultipleModeledMethodsPanel.name, () => { }), ).toHaveValue("source"); - await userEvent.click(screen.getByLabelText("Next modeling")); + await userEvent.click(nextButton); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeDisabled(); + expect(prevButton).toBeEnabled(); + expect(nextButton).toBeDisabled(); expect(screen.getByText("3/3")).toBeInTheDocument(); expect( @@ -499,24 +456,14 @@ describe(MultipleModeledMethodsPanel.name, () => { }), ).toHaveValue("local"); - await userEvent.click(screen.getByLabelText("Previous modeling")); + await userEvent.click(prevButton); await waitFor(() => { - expect( - screen - .getByLabelText("Next modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(nextButton).toBeEnabled(); }); - expect( - screen - .getByLabelText("Previous modeling") - .getElementsByTagName("input")[0], - ).toBeEnabled(); - expect( - screen.getByLabelText("Next modeling").getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(prevButton).toBeEnabled(); + expect(nextButton).toBeEnabled(); expect(screen.getByText("2/3")).toBeInTheDocument(); expect( @@ -574,12 +521,11 @@ describe(MultipleModeledMethodsPanel.name, () => { const render = createRender(modeledMethods); - it("can add modeling", () => { + it("can add modeling", async () => { render(); - expect( - screen.getByLabelText("Add modeling").getElementsByTagName("input")[0], - ).toBeEnabled(); + const addButton = await screen.findByLabelText("Add modeling"); + expect(addButton).toBeEnabled(); }); it("can delete first modeling", async () => { diff --git a/extensions/ql-vscode/src/view/model-alerts/ModelAlertsActions.tsx b/extensions/ql-vscode/src/view/model-alerts/ModelAlertsActions.tsx index e99689634d2..da73126d2fc 100644 --- a/extensions/ql-vscode/src/view/model-alerts/ModelAlertsActions.tsx +++ b/extensions/ql-vscode/src/view/model-alerts/ModelAlertsActions.tsx @@ -1,6 +1,6 @@ import { styled } from "styled-components"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { VariantAnalysisStatus } from "../../variant-analysis/shared/variant-analysis"; +import { VscodeButton } from "@vscode-elements/react-elements"; type ModelAlertsActionsProps = { variantAnalysisStatus: VariantAnalysisStatus; @@ -14,7 +14,7 @@ const Container = styled.div` gap: 1em; `; -const Button = styled(VSCodeButton)` +const Button = styled(VscodeButton)` white-space: nowrap; `; @@ -25,12 +25,12 @@ export const ModelAlertsActions = ({ return ( {variantAnalysisStatus === VariantAnalysisStatus.InProgress && ( - + Stop evaluation )} {variantAnalysisStatus === VariantAnalysisStatus.Canceling && ( - + Stopping evaluation )} diff --git a/extensions/ql-vscode/src/view/model-alerts/ModelAlertsResults.tsx b/extensions/ql-vscode/src/view/model-alerts/ModelAlertsResults.tsx index 8b27896d333..d10757e3748 100644 --- a/extensions/ql-vscode/src/view/model-alerts/ModelAlertsResults.tsx +++ b/extensions/ql-vscode/src/view/model-alerts/ModelAlertsResults.tsx @@ -1,7 +1,6 @@ import { styled } from "styled-components"; import type { ModelAlerts } from "../../model-editor/model-alerts/model-alerts"; import { Codicon } from "../common"; -import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"; import { useCallback, useEffect, useRef, useState } from "react"; import { formatDecimal } from "../../common/number"; import AnalysisAlertResult from "../variant-analysis/AnalysisAlertResult"; @@ -11,6 +10,7 @@ import { vscode } from "../vscode-api"; import { createModeledMethodKey } from "../../model-editor/modeled-method"; import type { ModeledMethod } from "../../model-editor/modeled-method"; import { Link } from "../common/Link"; +import { Badge } from "../common/Badge"; // This will ensure that these icons have a className which we can use in the TitleContainer const ExpandCollapseCodicon = styled(Codicon)``; @@ -103,7 +103,7 @@ export const ModelAlertsResults = ({ {!isExpanded && ( )} - {formatDecimal(modelAlerts.alerts.length)} + {formatDecimal(modelAlerts.alerts.length)} {modelAlerts.model.type} { ); return ( - + - Alphabetically - + Alphabetically + Number of results - + ); }; diff --git a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx index 662d739ad7b..f2f1065b4d9 100644 --- a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx @@ -7,11 +7,12 @@ import { calculateModeledPercentage } from "../../model-editor/shared/modeled-pe import { percentFormatter } from "./formatters"; import { Codicon } from "../common"; import { Mode } from "../../model-editor/shared/mode"; -import { VSCodeButton, VSCodeDivider } from "@vscode/webview-ui-toolkit/react"; +import { VscodeButton, VscodeDivider } from "@vscode-elements/react-elements"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; import { Tag } from "../common/Tag"; +import { ActionButton } from "../common/ActionButton/ActionButton"; const LibraryContainer = styled.div` background-color: var(--vscode-peekViewResult-background); @@ -34,9 +35,9 @@ const TitleContainer = styled.button` cursor: pointer; `; -const SectionDivider = styled(VSCodeDivider)` - padding-top: 0.3rem; - padding-bottom: 0.3rem; +const SectionDivider = styled(VscodeDivider)` + margin-top: 0.3rem; + margin-bottom: 0.8rem; `; const NameContainer = styled.div` @@ -170,16 +171,16 @@ export const LibraryRow = ({ {viewState.showGenerateButton && viewState.mode === Mode.Application && ( - + Model from source - + )} {viewState.mode === Mode.Application && ( - + Model dependency - + )} {isExpanded && ( @@ -200,9 +201,9 @@ export const LibraryRow = ({ /> - + {selectedSignatures.size === 0 ? "Save" : "Save selected"} - + > )} diff --git a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx index 22dc54d2a48..6e753c3532d 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx @@ -1,4 +1,3 @@ -import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { forwardRef, useCallback, @@ -10,6 +9,7 @@ import { import { styled } from "styled-components"; import { vscode } from "../vscode-api"; import { Link } from "../common/Link"; +import { ActionButton } from "../common/ActionButton/ActionButton"; import type { Method } from "../../model-editor/method"; import type { ModeledMethod } from "../../model-editor/modeled-method"; @@ -35,6 +35,7 @@ import { ModelOutputSuggestBox } from "./ModelOutputSuggestBox"; import { getModelsAsDataLanguage } from "../../model-editor/languages"; import { ModelAlertsIndicator } from "./ModelAlertsIndicator"; import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; +import { Badge } from "../common/Badge"; const ApiOrMethodRow = styled.div` min-height: calc(var(--input-height) * 1px); @@ -52,15 +53,16 @@ const ModelButtonsContainer = styled.div` gap: 1em; `; -const UsagesButton = styled(VSCodeBadge)` +const UsagesButton = styled(Badge)` cursor: pointer; + display: table; `; const ViewLink = styled(Link)` white-space: nowrap; `; -const CodiconRow = styled(VSCodeButton)` +const CodiconRow = styled(ActionButton)` min-height: calc(var(--input-height) * 1px); align-items: center; `; @@ -318,7 +320,6 @@ const ModelableMethodRow = forwardRef( > {index === 0 ? ( { event.stopPropagation(); @@ -330,7 +331,6 @@ const ModelableMethodRow = forwardRef( ) : ( { event.stopPropagation(); diff --git a/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx b/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx index 117d18903ca..a99bc815f1e 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelAlertsIndicator.tsx @@ -2,10 +2,10 @@ import { styled } from "styled-components"; import type { ModeledMethod } from "../../model-editor/modeled-method"; import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; -import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"; import { vscode } from "../vscode-api"; +import { Badge } from "../common/Badge"; -const ModelAlertsButton = styled(VSCodeBadge)` +const ModelAlertsButton = styled(Badge)` cursor: pointer; `; diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx index 6d9628cf0db..344a0d32d98 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import type { ToModelEditorMessage } from "../../common/interface-types"; -import { VSCodeButton, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"; +import { VscodeButton, VscodeCheckbox } from "@vscode-elements/react-elements"; import { styled } from "styled-components"; import type { Method } from "../../model-editor/method"; import type { ModeledMethod } from "../../model-editor/modeled-method"; @@ -331,27 +331,27 @@ export function ModelEditor({ - {selectedSignatures.size === 0 ? "Save all" : "Save selected"} - - + Deselect all - - + + Refresh - + {viewState.showGenerateButton && viewState.mode === Mode.Framework && ( - + Generate - + )} - Hide modeled methods - + diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEditorProgressRing.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEditorProgressRing.tsx index 3994caddf82..523e8fd32c8 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEditorProgressRing.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEditorProgressRing.tsx @@ -1,7 +1,7 @@ -import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"; +import { VscodeProgressRing } from "@vscode-elements/react-elements"; import { styled } from "styled-components"; -export const ModelEditorProgressRing = styled(VSCodeProgressRing)` +export const ModelEditorProgressRing = styled(VscodeProgressRing)` width: 16px; height: 16px; margin-right: 5px; diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEvaluation.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEvaluation.tsx index 95b94aa5411..ba0266c083f 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEvaluation.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEvaluation.tsx @@ -1,5 +1,4 @@ import { styled } from "styled-components"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import type { ModeledMethod } from "../../model-editor/modeled-method"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state"; @@ -7,6 +6,7 @@ import { modelEvaluationRunIsRunning } from "../../model-editor/shared/model-eva import { ModelEditorProgressRing } from "./ModelEditorProgressRing"; import { LinkIconButton } from "../common/LinkIconButton"; import { Link } from "../common/Link"; +import { VscodeButton } from "@vscode-elements/react-elements"; export type Props = { viewState: ModelEditorViewState; @@ -53,19 +53,19 @@ export const ModelEvaluation = ({ return ( <> {shouldShowEvaluateButton && ( - Evaluate - + )} {shouldShowStopButton && ( - + Stop evaluation - + )} {shouldShowEvaluationRunLink && ( diff --git a/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx b/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx index d52ab1306bf..69f9a0829b9 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx @@ -4,8 +4,8 @@ import type { ModeledMethod, TypeModeledMethod, } from "../../model-editor/modeled-method"; -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"; import { useDebounceCallback } from "../common/useDebounceCallback"; +import { VscodeTextfield } from "@vscode-elements/react-elements"; type Props = { modeledMethod: TypeModeledMethod; @@ -53,7 +53,7 @@ export const ModelTypeTextbox = ({ ); return ( - { modeledMethods: [], }); - const addButton = screen.queryByLabelText("Add new model"); + const addButton = await screen.findByLabelText("Add new model"); expect(addButton).toBeInTheDocument(); - expect(addButton?.getElementsByTagName("input")[0]).toBeDisabled(); + expect(addButton).toBeDisabled(); expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument(); }); @@ -255,9 +255,9 @@ describe(MethodRow.name, () => { modeledMethods: [{ ...modeledMethod, type: "none" }], }); - const addButton = screen.queryByLabelText("Add new model"); + const addButton = await screen.findByLabelText("Add new model"); expect(addButton).toBeInTheDocument(); - expect(addButton?.getElementsByTagName("input")[0]).toBeDisabled(); + expect(addButton).toBeDisabled(); expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument(); }); @@ -267,9 +267,9 @@ describe(MethodRow.name, () => { modeledMethods: [modeledMethod], }); - const addButton = screen.queryByLabelText("Add new model"); + const addButton = await screen.findByLabelText("Add new model"); expect(addButton).toBeInTheDocument(); - expect(addButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(addButton).toBeEnabled(); expect(screen.queryByLabelText("Remove model")).not.toBeInTheDocument(); }); @@ -282,16 +282,16 @@ describe(MethodRow.name, () => { ], }); - const addButton = screen.queryByLabelText("Add new model"); + const addButton = await screen.findByLabelText("Add new model"); expect(addButton).toBeInTheDocument(); - expect(addButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(addButton).toBeEnabled(); - const removeButton = screen.queryByLabelText("Remove model"); + const removeButton = await screen.findByLabelText("Remove model"); expect(removeButton).toBeInTheDocument(); - expect(removeButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(removeButton).toBeEnabled(); }); - it("shows add model button on first row and remove model button on all other rows", async () => { + it("shows add model button on first row and remove model button on all other rows", () => { render({ modeledMethods: [ { ...modeledMethod, type: "source" }, @@ -303,12 +303,12 @@ describe(MethodRow.name, () => { const addButtons = screen.queryAllByLabelText("Add new model"); expect(addButtons.length).toBe(1); - expect(addButtons[0]?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(addButtons[0]).toBeEnabled(); const removeButtons = screen.queryAllByLabelText("Remove model"); expect(removeButtons.length).toBe(3); for (const removeButton of removeButtons) { - expect(removeButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(removeButton).toBeEnabled(); } }); diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/ModelEvaluation.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/ModelEvaluation.spec.tsx index 24897bfa098..150ace05b53 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/ModelEvaluation.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/ModelEvaluation.spec.tsx @@ -1,4 +1,4 @@ -import { render as reactRender, screen } from "@testing-library/react"; +import { render as reactRender, screen, waitFor } from "@testing-library/react"; import type { Props } from "../ModelEvaluation"; import { ModelEvaluation } from "../ModelEvaluation"; import { createMockModelEditorViewState } from "../../../../test/factories/model-editor/view-state"; @@ -39,47 +39,51 @@ describe(ModelEvaluation.name, () => { }); describe("when showEvaluationUi is true", () => { - it("renders evaluation UI with 'Evaluate' button enabled", () => { + it("renders evaluation UI with 'Evaluate' button enabled", async () => { render(); - const evaluateButton = screen.queryByText("Evaluate"); + const evaluateButton = await screen.findByText("Evaluate"); expect(evaluateButton).toBeInTheDocument(); - expect(evaluateButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(evaluateButton).toBeEnabled(); expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument(); expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument(); }); - it("disables 'Evaluate' button when there are no custom models", () => { + it("disables 'Evaluate' button when there are no custom models", async () => { render({ modeledMethods: {}, }); - const evaluateButton = screen.queryByText("Evaluate"); + const evaluateButton = await screen.findByText("Evaluate"); expect(evaluateButton).toBeInTheDocument(); - expect(evaluateButton?.getElementsByTagName("input")[0]).toBeDisabled(); + await waitFor(() => { + expect(evaluateButton).toBeDisabled(); + }); expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument(); expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument(); }); - it("disables 'Evaluate' button when there are unsaved changes", () => { + it("disables 'Evaluate' button when there are unsaved changes", async () => { render({ modifiedSignatures: new Set([method.signature]), }); - const evaluateButton = screen.queryByText("Evaluate"); + const evaluateButton = await screen.findByText("Evaluate"); expect(evaluateButton).toBeInTheDocument(); - expect(evaluateButton?.getElementsByTagName("input")[0]).toBeDisabled(); + await waitFor(() => { + expect(evaluateButton).toBeDisabled(); + }); expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument(); expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument(); }); - it("renders 'Evaluate' button and 'Evaluation run' link when there is a completed evaluation", () => { + it("renders 'Evaluate' button and 'Evaluation run' link when there is a completed evaluation", async () => { render({ evaluationRun: { isPreparing: false, @@ -89,16 +93,16 @@ describe(ModelEvaluation.name, () => { }, }); - const evaluateButton = screen.queryByText("Evaluate"); + const evaluateButton = await screen.findByText("Evaluate"); expect(evaluateButton).toBeInTheDocument(); - expect(evaluateButton?.getElementsByTagName("input")[0]).toBeEnabled(); + expect(evaluateButton).toBeEnabled(); expect(screen.queryByText("Evaluation run")).toBeInTheDocument(); expect(screen.queryByText("Stop evaluation")).not.toBeInTheDocument(); }); - it("renders 'Stop evaluation' button when there is an in progress evaluation, but no variant analysis yet", () => { + it("renders 'Stop evaluation' button when there is an in progress evaluation, but no variant analysis yet", async () => { render({ evaluationRun: { isPreparing: true, @@ -106,18 +110,16 @@ describe(ModelEvaluation.name, () => { }, }); - const stopEvaluationButton = screen.queryByText("Stop evaluation"); + const stopEvaluationButton = await screen.findByText("Stop evaluation"); expect(stopEvaluationButton).toBeInTheDocument(); - expect( - stopEvaluationButton?.getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(stopEvaluationButton).toBeEnabled(); expect(screen.queryByText("Evaluation run")).not.toBeInTheDocument(); expect(screen.queryByText("Evaluate")).not.toBeInTheDocument(); }); - it("renders 'Stop evaluation' button and 'Evaluation run' link when there is an in progress evaluation with variant analysis", () => { + it("renders 'Stop evaluation' button and 'Evaluation run' link when there is an in progress evaluation with variant analysis", async () => { render({ evaluationRun: { isPreparing: false, @@ -127,11 +129,9 @@ describe(ModelEvaluation.name, () => { }, }); - const stopEvaluationButton = screen.queryByText("Stop evaluation"); + const stopEvaluationButton = await screen.findByText("Stop evaluation"); expect(stopEvaluationButton).toBeInTheDocument(); - expect( - stopEvaluationButton?.getElementsByTagName("input")[0], - ).toBeEnabled(); + expect(stopEvaluationButton).toBeEnabled(); expect(screen.queryByText("Evaluation run")).toBeInTheDocument(); diff --git a/extensions/ql-vscode/src/view/results/AlertTableResultRow.tsx b/extensions/ql-vscode/src/view/results/AlertTableResultRow.tsx index 3ae0e5badac..3ab31ed3f7f 100644 --- a/extensions/ql-vscode/src/view/results/AlertTableResultRow.tsx +++ b/extensions/ql-vscode/src/view/results/AlertTableResultRow.tsx @@ -13,7 +13,7 @@ import { SarifLocation } from "./locations/SarifLocation"; import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations"; import { AlertTablePathRow } from "./AlertTablePathRow"; import type { UserSettings } from "../../common/interface-types"; -import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"; +import { Badge } from "../common/Badge"; export interface Props { result: Result; @@ -109,7 +109,7 @@ export function AlertTableResultRow(props: Props) { /> {listUnordered} - {shortestPath} + {shortestPath} {msg} > diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index fc381fe016f..19edad08cc2 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -1,7 +1,7 @@ import type { ChangeEvent } from "react"; import { useCallback, useEffect, useState } from "react"; import { styled } from "styled-components"; -import { VSCodeBadge, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"; +import { VscodeCheckbox } from "@vscode-elements/react-elements"; import type { VariantAnalysisScannedRepositoryState } from "../../variant-analysis/shared/variant-analysis"; import { isCompletedAnalysisRepoStatus, @@ -27,6 +27,7 @@ import StarCount from "../common/StarCount"; import { useTelemetryOnChange } from "../common/telemetry"; import { DeterminateProgressRing } from "../common/DeterminateProgressRing"; import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format"; +import { Badge } from "../common/Badge"; // This will ensure that these icons have a className which we can use in the TitleContainer const ExpandCollapseCodicon = styled(Codicon)``; @@ -61,6 +62,10 @@ const MetadataContainer = styled.div` margin-left: auto; `; +const Checkbox = styled(VscodeCheckbox)` + margin-right: -9px; // VscodeCheckbox has 9px margin on the right by default +`; + type VisibilityProps = { isPrivate?: boolean; }; @@ -254,7 +259,7 @@ export const RepoRow = ({ disabled={disabled} aria-expanded={isExpanded} > - )} {resultsLoading && } - + {resultCount === undefined ? "-" : formatDecimal(resultCount)} - + {repository.fullName} diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesFilter.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesFilter.tsx index 56596b2ecbf..206b33586fb 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesFilter.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesFilter.tsx @@ -1,10 +1,13 @@ import { useCallback } from "react"; import { styled } from "styled-components"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { + VscodeOption, + VscodeSingleSelect, +} from "@vscode-elements/react-elements"; import { Codicon } from "../common"; import { FilterKey } from "../../variant-analysis/shared/variant-analysis-filter-sort"; -const Dropdown = styled(VSCodeDropdown)` +const Dropdown = styled(VscodeSingleSelect)` width: 100%; `; @@ -26,10 +29,10 @@ export const RepositoriesFilter = ({ value, onChange, className }: Props) => { ); return ( - + - All - With results + All + With results ); }; diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesResultFormat.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesResultFormat.tsx index 11ac814cc18..380599017eb 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesResultFormat.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesResultFormat.tsx @@ -1,10 +1,13 @@ import { useCallback } from "react"; import { styled } from "styled-components"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { + VscodeOption, + VscodeSingleSelect, +} from "@vscode-elements/react-elements"; import { Codicon } from "../common"; import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format"; -const Dropdown = styled(VSCodeDropdown)` +const Dropdown = styled(VscodeSingleSelect)` width: 100%; `; @@ -30,14 +33,14 @@ export const RepositoriesResultFormat = ({ ); return ( - + - + {ResultFormat.Alerts} - - + + {ResultFormat.RawResults} - + ); }; diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesSort.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesSort.tsx index d4383a3e1ea..96ec4c43dd4 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepositoriesSort.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepositoriesSort.tsx @@ -1,10 +1,13 @@ import { useCallback } from "react"; import { styled } from "styled-components"; -import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; +import { + VscodeOption, + VscodeSingleSelect, +} from "@vscode-elements/react-elements"; import { SortKey } from "../../variant-analysis/shared/variant-analysis-filter-sort"; import { Codicon } from "../common"; -const Dropdown = styled(VSCodeDropdown)` +const Dropdown = styled(VscodeSingleSelect)` width: 100%; `; @@ -26,13 +29,13 @@ export const RepositoriesSort = ({ value, onChange, className }: Props) => { ); return ( - + - Alphabetically - + Alphabetically + Number of results - - Popularity + + Popularity ); }; diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisActions.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisActions.tsx index ed1b473c66a..8a573761ef0 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisActions.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisActions.tsx @@ -1,6 +1,6 @@ import { styled } from "styled-components"; -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import { VariantAnalysisStatus } from "../../variant-analysis/shared/variant-analysis"; +import { VscodeButton } from "@vscode-elements/react-elements"; export type VariantAnalysisActionsProps = { variantAnalysisStatus: VariantAnalysisStatus; @@ -24,7 +24,7 @@ const Container = styled.div` gap: 1em; `; -const Button = styled(VSCodeButton)` +const Button = styled(VscodeButton)` white-space: nowrap; `; @@ -67,7 +67,7 @@ export const VariantAnalysisActions = ({ {showResultActions && ( <> @@ -96,7 +96,7 @@ export const VariantAnalysisActions = ({ )} {variantAnalysisStatus === VariantAnalysisStatus.InProgress && ( @@ -104,7 +104,7 @@ export const VariantAnalysisActions = ({ )} {variantAnalysisStatus === VariantAnalysisStatus.Canceling && ( - + Stopping query )} diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx index 469c7aa6d29..1f138ffd8cd 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisOutcomePanels.tsx @@ -2,11 +2,10 @@ import type { Dispatch, SetStateAction } from "react"; import { useState } from "react"; import { styled } from "styled-components"; import { - VSCodeBadge, - VSCodePanels, - VSCodePanelTab, - VSCodePanelView, -} from "@vscode/webview-ui-toolkit/react"; + VscodeTabHeader, + VscodeTabPanel, + VscodeTabs, +} from "@vscode-elements/react-elements"; import { formatDecimal } from "../../common/number"; import type { VariantAnalysis, @@ -21,6 +20,7 @@ import type { RepositoriesFilterSortState } from "../../variant-analysis/shared/ import { RepositoriesSearchSortRow } from "./RepositoriesSearchSortRow"; import { FailureReasonAlert } from "./FailureReasonAlert"; import { ResultFormat } from "../../variant-analysis/shared/variant-analysis-result-format"; +import { Badge } from "../common/Badge"; export type VariantAnalysisOutcomePanelProps = { variantAnalysis: VariantAnalysis; @@ -34,8 +34,27 @@ export type VariantAnalysisOutcomePanelProps = { setFilterSortState: Dispatch>; }; -const Tab = styled(VSCodePanelTab)` +const Tabs = styled(VscodeTabs)` + column-gap: 32px; + + > vscode-tab-header { + margin-right: 32px; + } +`; + +const TabHeader = styled(VscodeTabHeader)` text-transform: uppercase; + + > * { + // This copies the styles from @vscode/webview-ui-toolkit's VSCodePanelTab + &:last-child { + margin-left: 8px; + } + } +`; + +const TabPanel = styled(VscodeTabPanel)` + padding: 10px 6px; `; const WarningsContainer = styled.div` @@ -154,33 +173,31 @@ export const VariantAnalysisOutcomePanels = ({ onResultFormatChange={setResultFormat} variantAnalysisQueryKind={variantAnalysis.query.kind} /> - + {scannedReposCount > 0 && ( - + Analyzed - + {formatDecimal(variantAnalysis.scannedRepos?.length ?? 0)} - - - )} - {notFoundRepos?.repositoryCount && ( - - No access - - {formatDecimal(notFoundRepos.repositoryCount)} - - - )} - {noCodeqlDbRepos?.repositoryCount && ( - - No database - - {formatDecimal(noCodeqlDbRepos.repositoryCount)} - - + + )} + {notFoundRepos?.repositoryCount !== undefined && + notFoundRepos?.repositoryCount > 0 && ( + + No access + {formatDecimal(notFoundRepos.repositoryCount)} + + )} + {noCodeqlDbRepos?.repositoryCount !== undefined && + noCodeqlDbRepos?.repositoryCount > 0 && ( + + No database + {formatDecimal(noCodeqlDbRepos.repositoryCount)} + + )} {scannedReposCount > 0 && ( - + - - )} - {notFoundRepos?.repositoryCount && ( - - - - )} - {noCodeqlDbRepos?.repositoryCount && ( - - - + )} - + {notFoundRepos?.repositoryCount !== undefined && + notFoundRepos?.repositoryCount > 0 && ( + + + + )} + {noCodeqlDbRepos?.repositoryCount !== undefined && + noCodeqlDbRepos?.repositoryCount > 0 && ( + + + + )} + > ); }; diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx index b1f4df59a71..1c89400615d 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx @@ -1,9 +1,4 @@ -import { - act, - render as reactRender, - screen, - waitFor, -} from "@testing-library/react"; +import { act, render as reactRender, screen } from "@testing-library/react"; import { VariantAnalysisRepoStatus, VariantAnalysisScannedRepositoryDownloadStatus, @@ -403,7 +398,8 @@ describe(RepoRow.name, () => { status: VariantAnalysisRepoStatus.InProgress, }); - expect(screen.getByRole("checkbox")).toBeDisabled(); + const checkbox = await screen.findByRole("checkbox"); + expect(checkbox).toBeDisabled(); }); it("does not allow selecting the item if the item has not been downloaded", async () => { @@ -411,7 +407,8 @@ describe(RepoRow.name, () => { status: VariantAnalysisRepoStatus.Succeeded, }); - expect(screen.getByRole("checkbox")).toBeDisabled(); + const checkbox = await screen.findByRole("checkbox"); + expect(checkbox).toBeDisabled(); }); it("does not allow selecting the item if the item has not been downloaded successfully", async () => { @@ -423,11 +420,8 @@ describe(RepoRow.name, () => { }, }); - // It seems like sometimes the first render doesn't have the checkbox disabled - // Might be related to https://github.com/microsoft/vscode-webview-ui-toolkit/issues/404 - await waitFor(() => { - expect(screen.getByRole("checkbox")).toBeDisabled(); - }); + const checkbox = await screen.findByRole("checkbox"); + expect(checkbox).toBeDisabled(); }); it("allows selecting the item if the item has been downloaded", async () => { @@ -440,6 +434,7 @@ describe(RepoRow.name, () => { }, }); - expect(screen.getByRole("checkbox")).toBeEnabled(); + const checkbox = await screen.findByRole("checkbox"); + expect(checkbox).toBeEnabled(); }); }); diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx index b8a7bfcd539..3279ed446d2 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx @@ -1,4 +1,4 @@ -import { render as reactRender, screen, waitFor } from "@testing-library/react"; +import { render as reactRender, screen } from "@testing-library/react"; import { VariantAnalysisFailureReason, VariantAnalysisStatus, @@ -57,9 +57,6 @@ describe(VariantAnalysis.name, () => { const variantAnalysis = createMockVariantAnalysis({}); render({ variantAnalysis }); - await waitFor(() => screen.getByDisplayValue("All")); - await waitFor(() => screen.getByDisplayValue("Number of results")); - await postMessage({ t: "setFilterSortState", filterSortState: { @@ -69,8 +66,11 @@ describe(VariantAnalysis.name, () => { }, }); - expect(screen.getByDisplayValue("With results")).toBeInTheDocument(); - expect(screen.getByDisplayValue("Alphabetically")).toBeInTheDocument(); + const withResults = await screen.findByText("With results"); + expect(withResults).toBeInTheDocument(); + + const alphabetically = await screen.findByText("Alphabetically"); + expect(alphabetically).toBeInTheDocument(); expect(screen.queryByDisplayValue("All")).not.toBeInTheDocument(); expect( diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisActions.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisActions.spec.tsx index de19458646e..ff0a99099a3 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisActions.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisActions.spec.tsx @@ -50,9 +50,9 @@ describe(VariantAnalysisActions.name, () => { variantAnalysisStatus: VariantAnalysisStatus.Canceling, }); - const button = screen.getByText("Stopping query"); + const button = await screen.findByText("Stopping query"); expect(button).toBeInTheDocument(); - expect(button.getElementsByTagName("input")[0]).toBeDisabled(); + expect(button).toBeDisabled(); }); it("does not render a stop query button when canceling", async () => { diff --git a/extensions/ql-vscode/test/jest-config.ts b/extensions/ql-vscode/test/jest-config.ts index 5a6b6f64bef..3270067df7a 100644 --- a/extensions/ql-vscode/test/jest-config.ts +++ b/extensions/ql-vscode/test/jest-config.ts @@ -1,8 +1,13 @@ // These are all the packages that DO need to be transformed. All other packages will be ignored. // These pacakges all use ES modules, so need to be transformed -const transformScopes = ["@microsoft", "@octokit"]; +const transformScopes = [ + "@microsoft", + "@octokit", + "@vscode-elements", + "@lit", + "@lit-labs", +]; const transformPackages = [ - "@vscode/webview-ui-toolkit", "before-after-hook", "d3", "data-uri-to-buffer", @@ -11,13 +16,14 @@ const transformPackages = [ "fetch-blob", "formdata-polyfill", "internmap", + "lit", "nanoid", "p-queue", "p-timeout", "robust-predicates", "universal-user-agent", ]; -const transformWildcards = ["d3-(.*)"]; +const transformWildcards = ["d3-(.*)", "lit-(.*)"]; const transformPatterns = [ ...transformScopes.map((scope) => `${scope}/.+`), ...transformPackages,