Merge pull request #2817 from github/koesie10/model-editor-row-tests
Add tests for model editor row and grid components
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* A class that keeps track of which methods are in progress for each package.
|
||||
*
|
||||
* This class is immutable and therefore is safe to be used in a react useState hook.
|
||||
* This class is immutable and therefore is safe to be used in a React useState hook.
|
||||
*/
|
||||
export class InProgressMethods {
|
||||
// A map of in-progress method signatures for each package.
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { Mode } from "../../model-editor/shared/mode";
|
||||
import { LibraryRow as LibraryRowComponent } from "../../view/model-editor/LibraryRow";
|
||||
import { CallClassification } from "../../model-editor/method";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
import { createMockExtensionPack } from "../../../test/factories/model-editor/extension-pack";
|
||||
|
||||
export default {
|
||||
title: "CodeQL Model Editor/Library Row",
|
||||
component: LibraryRowComponent,
|
||||
} as Meta<typeof LibraryRowComponent>;
|
||||
|
||||
const Template: StoryFn<typeof LibraryRowComponent> = (args) => (
|
||||
<LibraryRowComponent {...args} />
|
||||
);
|
||||
|
||||
export const LibraryRow = Template.bind({});
|
||||
LibraryRow.args = {
|
||||
title: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
methods: [
|
||||
{
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
supported: true,
|
||||
supportedType: "summary",
|
||||
usages: Array(10).fill({
|
||||
label: "createQuery(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 56,
|
||||
},
|
||||
classification: CallClassification.Source,
|
||||
}),
|
||||
},
|
||||
{
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
supported: true,
|
||||
supportedType: "neutral",
|
||||
usages: Array(2).fill({
|
||||
label: "executeScalar(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 15,
|
||||
startColumn: 13,
|
||||
endLine: 15,
|
||||
endColumn: 85,
|
||||
},
|
||||
classification: CallClassification.Source,
|
||||
}),
|
||||
},
|
||||
{
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
supported: false,
|
||||
supportedType: "none",
|
||||
usages: Array(28).fill({
|
||||
label: "open(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 14,
|
||||
startColumn: 24,
|
||||
endLine: 14,
|
||||
endColumn: 35,
|
||||
},
|
||||
classification: CallClassification.Source,
|
||||
}),
|
||||
},
|
||||
{
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
supported: false,
|
||||
supportedType: "none",
|
||||
usages: Array(106).fill({
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 10,
|
||||
startColumn: 33,
|
||||
endLine: 10,
|
||||
endColumn: 88,
|
||||
classification: CallClassification.Test,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
supported: false,
|
||||
supportedType: "none",
|
||||
usages: [
|
||||
...Array(4).fill({
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
||||
startLine: 23,
|
||||
startColumn: 23,
|
||||
endLine: 23,
|
||||
endColumn: 36,
|
||||
},
|
||||
classification: CallClassification.Test,
|
||||
}),
|
||||
{
|
||||
label: "new Sql2o(...)",
|
||||
url: {
|
||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/build/generated/java/org/example/HelloControllerGenerated.java",
|
||||
startLine: 23,
|
||||
startColumn: 23,
|
||||
endLine: 23,
|
||||
endColumn: 36,
|
||||
},
|
||||
classification: CallClassification.Generated,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
modeledMethods: {
|
||||
"org.sql2o.Sql2o#Sql2o(String)": {
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Connection#createQuery(String)": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-manual",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
},
|
||||
"org.sql2o.Sql2o#open()": {
|
||||
type: "summary",
|
||||
input: "Argument[this]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "manual",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
},
|
||||
"org.sql2o.Query#executeScalar(Class)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
},
|
||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": {
|
||||
type: "neutral",
|
||||
input: "",
|
||||
output: "",
|
||||
kind: "",
|
||||
provenance: "df-generated",
|
||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "Sql2o",
|
||||
methodParameters: "(String,String,String)",
|
||||
},
|
||||
},
|
||||
modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]),
|
||||
inProgressMethods: new InProgressMethods(),
|
||||
viewState: {
|
||||
extensionPack: createMockExtensionPack(),
|
||||
showFlowGeneration: true,
|
||||
showLlmButton: true,
|
||||
mode: Mode.Application,
|
||||
},
|
||||
hideModeledMethods: false,
|
||||
};
|
||||
@@ -21,6 +21,8 @@ type Props = {
|
||||
disabled?: boolean;
|
||||
disabledPlaceholder?: string;
|
||||
onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;
|
||||
|
||||
"aria-label"?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -39,6 +41,7 @@ export function Dropdown({
|
||||
disabled,
|
||||
disabledPlaceholder,
|
||||
onChange,
|
||||
...props
|
||||
}: Props) {
|
||||
const disabledValue = disabledPlaceholder ?? DISABLED_VALUE;
|
||||
return (
|
||||
@@ -46,6 +49,7 @@ export function Dropdown({
|
||||
value={disabled ? disabledValue : value}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
>
|
||||
{disabled ? (
|
||||
<option key={disabledValue} value={disabledValue}>
|
||||
|
||||
@@ -9,9 +9,17 @@ type Props = {
|
||||
value: ModeledMethod["kind"] | undefined;
|
||||
disabled?: boolean;
|
||||
onChange: (value: ModeledMethod["kind"]) => void;
|
||||
|
||||
"aria-label"?: string;
|
||||
};
|
||||
|
||||
export const KindInput = ({ kinds, value, disabled, onChange }: Props) => {
|
||||
export const KindInput = ({
|
||||
kinds,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
...props
|
||||
}: Props) => {
|
||||
const options = useMemo(
|
||||
() => kinds.map((kind) => ({ value: kind, label: kind })),
|
||||
[kinds],
|
||||
@@ -42,6 +50,7 @@ export const KindInput = ({ kinds, value, disabled, onChange }: Props) => {
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
onChange={handleInput}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ const ButtonsContainer = styled.div`
|
||||
margin-right: 1rem;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
export type LibraryRowProps = {
|
||||
title: string;
|
||||
libraryVersion?: string;
|
||||
methods: Method[];
|
||||
@@ -110,7 +110,7 @@ export const LibraryRow = ({
|
||||
onStopGenerateFromLlmClick,
|
||||
onGenerateFromSourceClick,
|
||||
onModelDependencyClick,
|
||||
}: Props) => {
|
||||
}: LibraryRowProps) => {
|
||||
const modeledPercentage = useMemo(() => {
|
||||
return calculateModeledPercentage(methods);
|
||||
}, [methods]);
|
||||
|
||||
@@ -60,7 +60,7 @@ const modelTypeOptions: Array<{ value: ModeledMethodType; label: string }> = [
|
||||
{ value: "neutral", label: "Neutral" },
|
||||
];
|
||||
|
||||
type Props = {
|
||||
export type MethodRowProps = {
|
||||
method: Method;
|
||||
methodCanBeModeled: boolean;
|
||||
modeledMethod: ModeledMethod | undefined;
|
||||
@@ -70,7 +70,7 @@ type Props = {
|
||||
onChange: (method: Method, modeledMethod: ModeledMethod) => void;
|
||||
};
|
||||
|
||||
export const MethodRow = (props: Props) => {
|
||||
export const MethodRow = (props: MethodRowProps) => {
|
||||
const { methodCanBeModeled } = props;
|
||||
|
||||
if (methodCanBeModeled) {
|
||||
@@ -80,7 +80,7 @@ export const MethodRow = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
function ModelableMethodRow(props: Props) {
|
||||
function ModelableMethodRow(props: MethodRowProps) {
|
||||
const { method, modeledMethod, methodIsUnsaved, mode, onChange } = props;
|
||||
|
||||
const argumentsList = useMemo(() => {
|
||||
@@ -203,7 +203,7 @@ function ModelableMethodRow(props: Props) {
|
||||
const modelingStatus = getModelingStatus(modeledMethod, methodIsUnsaved);
|
||||
|
||||
return (
|
||||
<VSCodeDataGridRow>
|
||||
<VSCodeDataGridRow data-testid="modelable-method-row">
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<ModelingStatusIndicator status={modelingStatus} />
|
||||
<MethodClassifications method={method} />
|
||||
@@ -239,6 +239,7 @@ function ModelableMethodRow(props: Props) {
|
||||
value={modeledMethod?.type ?? "none"}
|
||||
options={modelTypeOptions}
|
||||
onChange={handleTypeInput}
|
||||
aria-label="Model type"
|
||||
/>
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={3}>
|
||||
@@ -247,6 +248,7 @@ function ModelableMethodRow(props: Props) {
|
||||
options={inputOptions}
|
||||
disabled={!showInputCell}
|
||||
onChange={handleInputInput}
|
||||
aria-label="Input"
|
||||
/>
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={4}>
|
||||
@@ -255,6 +257,7 @@ function ModelableMethodRow(props: Props) {
|
||||
options={outputOptions}
|
||||
disabled={!showOutputCell}
|
||||
onChange={handleOutputInput}
|
||||
aria-label="Output"
|
||||
/>
|
||||
</VSCodeDataGridCell>
|
||||
<VSCodeDataGridCell gridColumn={5}>
|
||||
@@ -263,6 +266,7 @@ function ModelableMethodRow(props: Props) {
|
||||
value={modeledMethod?.kind}
|
||||
disabled={!showKindCell}
|
||||
onChange={handleKindChange}
|
||||
aria-label="Kind"
|
||||
/>
|
||||
</VSCodeDataGridCell>
|
||||
</>
|
||||
@@ -271,7 +275,7 @@ function ModelableMethodRow(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function UnmodelableMethodRow(props: Props) {
|
||||
function UnmodelableMethodRow(props: MethodRowProps) {
|
||||
const { method, mode } = props;
|
||||
|
||||
const jumpToUsage = useCallback(
|
||||
@@ -280,7 +284,7 @@ function UnmodelableMethodRow(props: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<VSCodeDataGridRow>
|
||||
<VSCodeDataGridRow data-testid="unmodelable-method-row">
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<ModelingStatusIndicator status="saved" />
|
||||
<MethodName {...props.method} />
|
||||
|
||||
@@ -15,7 +15,7 @@ import { HiddenMethodsRow } from "./HiddenMethodsRow";
|
||||
|
||||
export const GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr";
|
||||
|
||||
type Props = {
|
||||
export type ModeledMethodDataGridProps = {
|
||||
packageName: string;
|
||||
methods: Method[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
@@ -35,7 +35,7 @@ export const ModeledMethodDataGrid = ({
|
||||
mode,
|
||||
hideModeledMethods,
|
||||
onChange,
|
||||
}: Props) => {
|
||||
}: ModeledMethodDataGridProps) => {
|
||||
const [methodsWithModelability, numHiddenMethods]: [
|
||||
Array<{ method: Method; methodCanBeModeled: boolean }>,
|
||||
number,
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||
|
||||
type Props = {
|
||||
export type ModeledMethodsListProps = {
|
||||
methods: Method[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
@@ -54,7 +54,7 @@ export const ModeledMethodsList = ({
|
||||
onStopGenerateFromLlmClick,
|
||||
onGenerateFromSourceClick,
|
||||
onModelDependencyClick,
|
||||
}: Props) => {
|
||||
}: ModeledMethodsListProps) => {
|
||||
const grouped = useMemo(
|
||||
() => groupMethods(methods, viewState.mode),
|
||||
[methods, viewState.mode],
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/data-extension/method-factories";
|
||||
import { LibraryRow, LibraryRowProps } from "../LibraryRow";
|
||||
import { InProgressMethods } from "../../../model-editor/shared/in-progress-methods";
|
||||
import { createMockExtensionPack } from "../../../../test/factories/model-editor/extension-pack";
|
||||
import { Mode } from "../../../model-editor/shared/mode";
|
||||
import { ModelEditorViewState } from "../../../model-editor/shared/view-state";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
describe(LibraryRow.name, () => {
|
||||
const method = createMethod();
|
||||
const onChange = jest.fn();
|
||||
const onSaveModelClick = jest.fn();
|
||||
const onGenerateFromLlmClick = jest.fn();
|
||||
const onStopGenerateFromLlmClick = jest.fn();
|
||||
const onModelDependencyClick = jest.fn();
|
||||
|
||||
const viewState: ModelEditorViewState = {
|
||||
mode: Mode.Application,
|
||||
showFlowGeneration: false,
|
||||
showLlmButton: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
};
|
||||
|
||||
const render = (props: Partial<LibraryRowProps> = {}) =>
|
||||
reactRender(
|
||||
<LibraryRow
|
||||
title="sql2o"
|
||||
libraryVersion="1.6.0"
|
||||
methods={[method]}
|
||||
modeledMethods={{
|
||||
[method.signature]: {
|
||||
...method,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
}}
|
||||
modifiedSignatures={new Set([method.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
viewState={viewState}
|
||||
hideModeledMethods={false}
|
||||
onChange={onChange}
|
||||
onSaveModelClick={onSaveModelClick}
|
||||
onGenerateFromLlmClick={onGenerateFromLlmClick}
|
||||
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
|
||||
onGenerateFromSourceClick={jest.fn()}
|
||||
onModelDependencyClick={onModelDependencyClick}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
it("renders the row", () => {
|
||||
render();
|
||||
|
||||
expect(screen.queryByText("sql2o@1.6.0")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Model from source")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Model with AI")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the row when flow generation is enabled", () => {
|
||||
render({
|
||||
viewState: {
|
||||
...viewState,
|
||||
showFlowGeneration: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByText("Model from source")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Model with AI")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the row when LLM is enabled", () => {
|
||||
render({
|
||||
viewState: {
|
||||
...viewState,
|
||||
showLlmButton: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByText("Model from source")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Model with AI")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the row when flow generation and LLM are enabled", () => {
|
||||
render({
|
||||
viewState: {
|
||||
...viewState,
|
||||
showFlowGeneration: true,
|
||||
showLlmButton: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.queryByText("Model from source")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Model with AI")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Model dependency")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("can expand the row", async () => {
|
||||
render();
|
||||
|
||||
expect(screen.queryByText("Save")).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole("button", {
|
||||
name: /sql2o@1.6.0/,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText("Save")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
getAllByRole,
|
||||
render as reactRender,
|
||||
screen,
|
||||
} from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/data-extension/method-factories";
|
||||
import { Mode } from "../../../model-editor/shared/mode";
|
||||
import { MethodRow, MethodRowProps } from "../MethodRow";
|
||||
import { ModeledMethod } from "../../../model-editor/modeled-method";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
describe(MethodRow.name, () => {
|
||||
const method = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
supported: false,
|
||||
});
|
||||
const modeledMethod: ModeledMethod = {
|
||||
...method,
|
||||
type: "summary",
|
||||
input: "Argument[0]",
|
||||
output: "ReturnValue",
|
||||
kind: "taint",
|
||||
provenance: "df-generated",
|
||||
};
|
||||
const onChange = jest.fn();
|
||||
|
||||
const render = (props: Partial<MethodRowProps> = {}) =>
|
||||
reactRender(
|
||||
<MethodRow
|
||||
method={method}
|
||||
methodCanBeModeled={true}
|
||||
modeledMethod={modeledMethod}
|
||||
methodIsUnsaved={false}
|
||||
modelingInProgress={false}
|
||||
mode={Mode.Application}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
it("renders a modelable method", () => {
|
||||
render();
|
||||
|
||||
expect(screen.queryAllByRole("combobox")).toHaveLength(4);
|
||||
expect(screen.getByLabelText("Method modeled")).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("Loading")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("can change the kind", async () => {
|
||||
render();
|
||||
|
||||
onChange.mockReset();
|
||||
|
||||
expect(screen.getByRole("combobox", { name: "Kind" })).toHaveValue("taint");
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByRole("combobox", { name: "Kind" }),
|
||||
"value",
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(method, {
|
||||
...modeledMethod,
|
||||
kind: "value",
|
||||
});
|
||||
});
|
||||
|
||||
it("has the correct input options", () => {
|
||||
render();
|
||||
|
||||
const inputDropdown = screen.getByRole("combobox", { name: "Input" });
|
||||
expect(inputDropdown).toHaveValue("Argument[0]");
|
||||
|
||||
const options = getAllByRole(inputDropdown, "option");
|
||||
expect(options).toHaveLength(2);
|
||||
expect(options[0]).toHaveTextContent("Argument[this]");
|
||||
expect(options[1]).toHaveTextContent("Argument[0]");
|
||||
});
|
||||
|
||||
it("has the correct output options", () => {
|
||||
render();
|
||||
|
||||
const inputDropdown = screen.getByRole("combobox", { name: "Output" });
|
||||
expect(inputDropdown).toHaveValue("ReturnValue");
|
||||
|
||||
const options = getAllByRole(inputDropdown, "option");
|
||||
expect(options).toHaveLength(3);
|
||||
expect(options[0]).toHaveTextContent("ReturnValue");
|
||||
expect(options[1]).toHaveTextContent("Argument[this]");
|
||||
expect(options[2]).toHaveTextContent("Argument[0]");
|
||||
});
|
||||
|
||||
it("shows the modeling status indicator when unsaved", () => {
|
||||
render({
|
||||
methodIsUnsaved: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByLabelText("Changes have not been saved"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the modeling status indicator when unmodeled", () => {
|
||||
render({
|
||||
modeledMethod: undefined,
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the in progress indicator when in progress", () => {
|
||||
render({
|
||||
modelingInProgress: true,
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText("Loading")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders an unmodelable method", () => {
|
||||
render({
|
||||
methodCanBeModeled: false,
|
||||
modeledMethod: undefined,
|
||||
});
|
||||
|
||||
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Method already modeled")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/data-extension/method-factories";
|
||||
import { InProgressMethods } from "../../../model-editor/shared/in-progress-methods";
|
||||
import { Mode } from "../../../model-editor/shared/mode";
|
||||
import {
|
||||
ModeledMethodDataGrid,
|
||||
ModeledMethodDataGridProps,
|
||||
} from "../ModeledMethodDataGrid";
|
||||
|
||||
describe(ModeledMethodDataGrid.name, () => {
|
||||
const method1 = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
supported: false,
|
||||
});
|
||||
const method2 = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
supported: false,
|
||||
});
|
||||
const method3 = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
supported: true,
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
|
||||
const render = (props: Partial<ModeledMethodDataGridProps> = {}) =>
|
||||
reactRender(
|
||||
<ModeledMethodDataGrid
|
||||
packageName="sql2o"
|
||||
methods={[method1, method2, method3]}
|
||||
modeledMethods={{
|
||||
[method1.signature]: {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
}}
|
||||
modifiedSignatures={new Set([method1.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
mode={Mode.Application}
|
||||
hideModeledMethods={false}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
it("renders the modeled and unmodeled rows", () => {
|
||||
render();
|
||||
|
||||
expect(screen.getAllByTestId("modelable-method-row")).toHaveLength(2);
|
||||
expect(screen.queryByTestId("unmodelable-method-row")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the modeled rows when hideModeledMethods is set", () => {
|
||||
render({
|
||||
hideModeledMethods: true,
|
||||
});
|
||||
|
||||
expect(screen.getAllByTestId("modelable-method-row")).toHaveLength(2);
|
||||
expect(
|
||||
screen.queryByTestId("unmodelable-method-row"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("And 1 method modeled in other CodeQL packs"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render rows when no methods are modelable", () => {
|
||||
render({
|
||||
methods: [method3],
|
||||
modifiedSignatures: new Set(),
|
||||
hideModeledMethods: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId("modelable-method-row"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("unmodelable-method-row"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("1 method modeled in other CodeQL packs"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import { createMethod } from "../../../../test/factories/data-extension/method-factories";
|
||||
import { InProgressMethods } from "../../../model-editor/shared/in-progress-methods";
|
||||
import { createMockExtensionPack } from "../../../../test/factories/model-editor/extension-pack";
|
||||
import { Mode } from "../../../model-editor/shared/mode";
|
||||
import { ModelEditorViewState } from "../../../model-editor/shared/view-state";
|
||||
import {
|
||||
ModeledMethodsList,
|
||||
ModeledMethodsListProps,
|
||||
} from "../ModeledMethodsList";
|
||||
|
||||
describe(ModeledMethodsList.name, () => {
|
||||
const method1 = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Connection#createQuery(String)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Connection",
|
||||
methodName: "createQuery",
|
||||
methodParameters: "(String)",
|
||||
});
|
||||
const method2 = createMethod({
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Query",
|
||||
methodName: "executeScalar",
|
||||
methodParameters: "(Class)",
|
||||
});
|
||||
const method3 = createMethod({
|
||||
library: "rt",
|
||||
libraryVersion: "",
|
||||
signature: "java.io.PrintStream#println(String)",
|
||||
packageName: "java.io",
|
||||
typeName: "PrintStream",
|
||||
methodName: "println",
|
||||
methodParameters: "(String)",
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
const onSaveModelClick = jest.fn();
|
||||
const onGenerateFromLlmClick = jest.fn();
|
||||
const onStopGenerateFromLlmClick = jest.fn();
|
||||
const onModelDependencyClick = jest.fn();
|
||||
|
||||
const viewState: ModelEditorViewState = {
|
||||
mode: Mode.Application,
|
||||
showFlowGeneration: false,
|
||||
showLlmButton: false,
|
||||
extensionPack: createMockExtensionPack(),
|
||||
};
|
||||
|
||||
const render = (props: Partial<ModeledMethodsListProps> = {}) =>
|
||||
reactRender(
|
||||
<ModeledMethodsList
|
||||
methods={[method1, method2, method3]}
|
||||
modeledMethods={{
|
||||
[method1.signature]: {
|
||||
...method1,
|
||||
type: "sink",
|
||||
input: "Argument[0]",
|
||||
output: "",
|
||||
kind: "jndi-injection",
|
||||
provenance: "df-generated",
|
||||
},
|
||||
}}
|
||||
modifiedSignatures={new Set([method1.signature])}
|
||||
inProgressMethods={new InProgressMethods()}
|
||||
viewState={viewState}
|
||||
hideModeledMethods={false}
|
||||
onChange={onChange}
|
||||
onSaveModelClick={onSaveModelClick}
|
||||
onGenerateFromLlmClick={onGenerateFromLlmClick}
|
||||
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
|
||||
onGenerateFromSourceClick={jest.fn()}
|
||||
onModelDependencyClick={onModelDependencyClick}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
|
||||
it("renders the rows", () => {
|
||||
render();
|
||||
|
||||
expect(screen.getByText("sql2o@1.6.0")).toBeInTheDocument();
|
||||
expect(screen.getByText("Java Runtime")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -3,40 +3,21 @@ import {
|
||||
Method,
|
||||
CallClassification,
|
||||
} from "../../../src/model-editor/method";
|
||||
import { ModeledMethodType } from "../../../src/model-editor/modeled-method";
|
||||
import { ResolvableLocationValue } from "../../../src/common/bqrs-cli-types";
|
||||
|
||||
export function createMethod({
|
||||
library = "sql2o-1.6.0.jar",
|
||||
supported = true,
|
||||
supportedType = "summary" as ModeledMethodType,
|
||||
usages = [],
|
||||
signature = "org.sql2o.Sql2o#open()",
|
||||
packageName = "org.sql2o",
|
||||
typeName = "Sql2o",
|
||||
methodName = "open",
|
||||
methodParameters = "()",
|
||||
}: {
|
||||
library?: string;
|
||||
supported?: boolean;
|
||||
supportedType?: ModeledMethodType;
|
||||
usages?: Usage[];
|
||||
signature?: string;
|
||||
packageName?: string;
|
||||
typeName?: string;
|
||||
methodName?: string;
|
||||
methodParameters?: string;
|
||||
} = {}): Method {
|
||||
export function createMethod(data: Partial<Method> = {}): Method {
|
||||
return {
|
||||
library,
|
||||
supported,
|
||||
supportedType,
|
||||
usages,
|
||||
signature,
|
||||
packageName,
|
||||
typeName,
|
||||
methodName,
|
||||
methodParameters,
|
||||
library: "sql2o",
|
||||
libraryVersion: "1.6.0",
|
||||
supported: true,
|
||||
supportedType: "summary",
|
||||
usages: [],
|
||||
signature: "org.sql2o.Sql2o#open()",
|
||||
packageName: "org.sql2o",
|
||||
typeName: "Sql2o",
|
||||
methodName: "open",
|
||||
methodParameters: "()",
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ExtensionPack } from "../../../src/model-editor/shared/extension-pack";
|
||||
import { join } from "path";
|
||||
|
||||
export function createMockExtensionPack({
|
||||
path = "/path/to/extension-pack",
|
||||
...data
|
||||
}: Partial<ExtensionPack> = {}): ExtensionPack {
|
||||
return {
|
||||
path,
|
||||
yamlPath: join(path, "codeql-pack.yml"),
|
||||
name: "sql2o",
|
||||
version: "0.0.0",
|
||||
language: "java",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
...data,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user