Skip to content

Commit 735c148

Browse files
committed
fix test assertion
1 parent 775501e commit 735c148

File tree

5 files changed

+67
-33
lines changed

5 files changed

+67
-33
lines changed

src/features/workspace/components/__tests__/workspace-preferred-model.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { screen, waitFor } from "@testing-library/react";
33
import { WorkspacePreferredModel } from "../workspace-preferred-model";
44
import userEvent from "@testing-library/user-event";
55

6-
test("render model overrides", () => {
6+
test("render model overrides", async () => {
77
render(
88
<WorkspacePreferredModel
99
isArchived={false}
@@ -19,7 +19,10 @@ test("render model overrides", () => {
1919
expect(
2020
screen.getByRole("button", { name: /select the model/i }),
2121
).toBeVisible();
22-
expect(screen.getByRole("button", { name: /save/i })).toBeVisible();
22+
23+
await waitFor(() => {
24+
expect(screen.getByRole("button", { name: /save/i })).toBeVisible();
25+
});
2326
});
2427

2528
test("submit preferred model", async () => {

src/features/workspace/components/workspace-custom-instructions.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,11 @@ function useCustomInstructionsValue({
123123
options: V1GetWorkspaceCustomInstructionsData;
124124
queryClient: QueryClient;
125125
}) {
126-
const formState = useFormState({ prompt: initialValue });
126+
const initialFormValues = useMemo(
127+
() => ({ prompt: initialValue }),
128+
[initialValue],
129+
);
130+
const formState = useFormState(initialFormValues);
127131
const { values, updateFormValues } = formState;
128132

129133
// Subscribe to changes in the workspace system prompt value in the query cache

src/features/workspace/components/workspace-preferred-model.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
Alert,
3-
Button,
43
Card,
54
CardBody,
65
CardFooter,
@@ -16,6 +15,7 @@ import { FormEvent } from "react";
1615
import { usePreferredModelWorkspace } from "../hooks/use-preferred-preferred-model";
1716
import { Select, SelectButton } from "@stacklok/ui-kit";
1817
import { useQueryListAllModelsForAllProviders } from "@/hooks/use-query-list-all-models-for-all-providers";
18+
import { FormButtons } from "@/components/FormButtons";
1919

2020
function MissingProviderBanner() {
2121
return (
@@ -39,11 +39,10 @@ export function WorkspacePreferredModel({
3939
workspaceName: string;
4040
isArchived: boolean | undefined;
4141
}) {
42-
const { preferredModel, setPreferredModel, isPending } =
43-
usePreferredModelWorkspace(workspaceName);
42+
const { formState, isPending } = usePreferredModelWorkspace(workspaceName);
4443
const { mutateAsync } = useMutationPreferredModelWorkspace();
4544
const { data: providerModels = [] } = useQueryListAllModelsForAllProviders();
46-
const { model, provider_id } = preferredModel;
45+
const { model, provider_id } = formState.values.preferredModel;
4746
const isModelsEmpty = !isPending && providerModels.length === 0;
4847

4948
const handleSubmit = (event: FormEvent) => {
@@ -84,16 +83,18 @@ export function WorkspacePreferredModel({
8483
isRequired
8584
isDisabled={isModelsEmpty}
8685
className="w-full"
87-
selectedKey={preferredModel?.model}
86+
selectedKey={formState.values.preferredModel?.model}
8887
placeholder="Select the model"
8988
onSelectionChange={(model) => {
9089
const preferredModelProvider = providerModels.find(
9190
(item) => item.name === model,
9291
);
9392
if (preferredModelProvider) {
94-
setPreferredModel({
95-
model: preferredModelProvider.name,
96-
provider_id: preferredModelProvider.provider_id,
93+
formState.updateFormValues({
94+
preferredModel: {
95+
model: preferredModelProvider.name,
96+
provider_id: preferredModelProvider.provider_id,
97+
},
9798
});
9899
}
99100
}}
@@ -109,9 +110,11 @@ export function WorkspacePreferredModel({
109110
</div>
110111
</CardBody>
111112
<CardFooter className="justify-end">
112-
<Button isDisabled={isArchived || isModelsEmpty} type="submit">
113-
Save
114-
</Button>
113+
<FormButtons
114+
isPending={isPending}
115+
formState={formState}
116+
canSubmit={!isArchived}
117+
/>
115118
</CardFooter>
116119
</Card>
117120
</Form>

src/features/workspace/hooks/use-preferred-preferred-model.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { MuxRule, V1GetWorkspaceMuxesData } from "@/api/generated";
22
import { v1GetWorkspaceMuxesOptions } from "@/api/generated/@tanstack/react-query.gen";
3+
import { useFormState } from "@/hooks/useFormState";
34
import { useQuery } from "@tanstack/react-query";
4-
import { useEffect, useMemo, useState } from "react";
5+
import { useMemo } from "react";
56

67
type ModelRule = Omit<MuxRule, "matcher_type" | "matcher"> & {};
78

@@ -21,8 +22,6 @@ const usePreferredModel = (options: {
2122
};
2223

2324
export const usePreferredModelWorkspace = (workspaceName: string) => {
24-
const [preferredModel, setPreferredModel] =
25-
useState<ModelRule>(DEFAULT_STATE);
2625
const options: V1GetWorkspaceMuxesData &
2726
Omit<V1GetWorkspaceMuxesData, "body"> = useMemo(
2827
() => ({
@@ -31,12 +30,10 @@ export const usePreferredModelWorkspace = (workspaceName: string) => {
3130
[workspaceName],
3231
);
3332
const { data, isPending } = usePreferredModel(options);
33+
const providerModel = data?.[0];
34+
const formState = useFormState<{ preferredModel: ModelRule }>({
35+
preferredModel: providerModel ?? DEFAULT_STATE,
36+
});
3437

35-
useEffect(() => {
36-
const providerModel = data?.[0];
37-
38-
setPreferredModel(providerModel ?? DEFAULT_STATE);
39-
}, [data, setPreferredModel]);
40-
41-
return { preferredModel, setPreferredModel, isPending };
38+
return { isPending, formState };
4239
};

src/hooks/useFormState.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isEqual } from "lodash";
2-
import { useState } from "react";
2+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
33

44
export type FormState<T> = {
55
values: T;
@@ -8,23 +8,50 @@ export type FormState<T> = {
88
isDirty: boolean;
99
};
1010

11+
function useDeepMemo<T>(value: T): T {
12+
const ref = useRef<T>(value);
13+
if (!isEqual(ref.current, value)) {
14+
ref.current = value;
15+
}
16+
return ref.current;
17+
}
18+
1119
export function useFormState<Values extends Record<string, unknown>>(
1220
initialValues: Values,
1321
): FormState<Values> {
22+
const memoizedInitialValues = useDeepMemo(initialValues);
23+
1424
// this could be replaced with some form library later
15-
const [values, setValues] = useState<Values>(initialValues);
16-
const updateFormValues = (newState: Partial<Values>) => {
25+
const [values, setValues] = useState<Values>(memoizedInitialValues);
26+
const [originalValues, setOriginalValues] = useState<Values>(values);
27+
28+
useEffect(() => {
29+
// this logic supports the use case when the initialValues change
30+
// due to an async request for instance
31+
setOriginalValues(memoizedInitialValues);
32+
setValues(memoizedInitialValues);
33+
}, [memoizedInitialValues]);
34+
35+
const updateFormValues = useCallback((newState: Partial<Values>) => {
1736
setValues((prevState: Values) => ({
1837
...prevState,
1938
...newState,
2039
}));
21-
};
40+
}, []);
41+
42+
const resetForm = useCallback(() => {
43+
setValues(originalValues);
44+
}, [originalValues]);
2245

23-
const resetForm = () => {
24-
setValues(initialValues);
25-
};
46+
const isDirty = useMemo(
47+
() => !isEqual(values, originalValues),
48+
[values, originalValues],
49+
);
2650

27-
const isDirty = !isEqual(values, initialValues);
51+
const formState = useMemo(
52+
() => ({ values, updateFormValues, resetForm, isDirty }),
53+
[values, updateFormValues, resetForm, isDirty],
54+
);
2855

29-
return { values, updateFormValues, resetForm, isDirty };
56+
return formState;
3057
}

0 commit comments

Comments
 (0)