Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "vitest"
},
"dependencies": {
"@testing-library/react": "^16.3.0",
"@types/lodash-es": "^4.17.12",
"antd": "^5.27.1",
"firebase": "^11.2.0",
Expand All @@ -33,6 +34,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"globals": "^15.11.0",
"jsdom": "^27.2.0",
"typescript": "~5.6.2",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9",
Expand Down
2 changes: 1 addition & 1 deletion src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Actions = {

export type RecipeStore = RecipeState & UIState & Actions;

const INITIAL_RECIPE_ID = "peroxisome_v_gradient_packing";
export const INITIAL_RECIPE_ID = "peroxisome_v_gradient_packing";

const initialState: RecipeState & UIState = {
selectedRecipeId: INITIAL_RECIPE_ID,
Expand Down
182 changes: 182 additions & 0 deletions src/test/store.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { act, renderHook } from "@testing-library/react";
import { expect, test } from "vitest";
import { useRecipeStore, INITIAL_RECIPE_ID } from "../state/store";
import { EMPTY_PACKING_RESULT } from "../state/constants";


test("recipeStore loads default state", () => {
const { result } = renderHook(() => useRecipeStore());
const store = result.current;

expect(store.selectedRecipeId).toBe(INITIAL_RECIPE_ID);
expect(store.isPacking).toBe(false);
expect(store.recipes).toBeDefined();
expect(store.inputOptions).toBeDefined();
expect(store.packingResults).toBeDefined();
expect(store.packingResults[INITIAL_RECIPE_ID]).toEqual(EMPTY_PACKING_RESULT);
});

test("selectedRecipeId defaults to INITIAL_RECIPE_ID when invalid id selected", () => {
const { result } = renderHook(() => useRecipeStore());
const store = result.current;

act(() => {
store.selectRecipe("non-existent-recipe-id");
});

expect(store.selectedRecipeId).toBe(INITIAL_RECIPE_ID);
});

test("recipeStore loads all recipes", async () => {
const { result } = renderHook(() => useRecipeStore());

await result.current.loadInputOptions();
await result.current.loadAllRecipes();

expect(Object.keys(result.current.inputOptions)).toHaveLength(5);

for (const recipeId of Object.keys(result.current.inputOptions)) {
await result.current.selectRecipe(recipeId);
expect(result.current.recipes[recipeId]).toBeDefined();
expect(result.current.recipes[recipeId].recipeId).toBe(recipeId);
expect(result.current.selectedRecipeId).toBe(recipeId);
}
expect(Object.keys(result.current.recipes)).toHaveLength(5);
});

test("editRecipe updates recipe object", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId = INITIAL_RECIPE_ID;
await result.current.selectRecipe(recipeId);

const path = "composition.membrane.count";
const initialValue = 1;
expect(result.current.getCurrentValue(path)).toBe(initialValue);

const newValue = 3;
act(() => {
result.current.editRecipe(recipeId, path, newValue);
});
expect(result.current.getCurrentValue(path)).toBe(newValue);
});

test("restoreRecipeDefault resets recipe to initial state", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId = INITIAL_RECIPE_ID;
await result.current.selectRecipe(recipeId);

const path = "composition.membrane.count";
const initialValue = 1;

act(() => {
result.current.editRecipe(recipeId, path, 6);
result.current.restoreRecipeDefault(recipeId);
});
expect(result.current.getCurrentValue(path)).toBe(initialValue);
});

test("editing one recipe does not affect others", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId1 = INITIAL_RECIPE_ID;

// Select a different recipe ID for recipeId2
const recipeId2 = Object.keys(result.current.inputOptions)[1];

await result.current.selectRecipe(recipeId1);
const path = "composition.membrane.count";
const initialCount = 1;
expect(result.current.getCurrentValue(path)).toBe(initialCount);

await result.current.selectRecipe(recipeId2);
expect(result.current.getCurrentValue(path)).toBe(initialCount);

const newValue1 = 5;
await result.current.selectRecipe(recipeId1);
act(() => {
result.current.editRecipe(recipeId1, path, newValue1);
});
expect(result.current.getCurrentValue(path)).toBe(newValue1);

// Switch back to recipeId2 and verify its value is unchanged
await result.current.selectRecipe(recipeId2);
expect(result.current.getCurrentValue(path)).toBe(initialCount);

// Switch back to recipeId1 and verify its value is the edited one
await result.current.selectRecipe(recipeId1);
expect(result.current.getCurrentValue(path)).toBe(newValue1);
});

test("setJobLogs updates job logs for current recipe", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId = INITIAL_RECIPE_ID;
await result.current.selectRecipe(recipeId);

const logs = "Some Error Message";

act(() => {
result.current.setJobLogs(logs);
});

expect(result.current.packingResults[recipeId].jobLogs).toBe(logs);

for (const recipeId of Object.keys(result.current.inputOptions)) {
if (recipeId !== INITIAL_RECIPE_ID) {
// No other job logs should have been updated to `logs`
expect(result.current.packingResults[recipeId]?.jobLogs).not.toBe(logs);
}
}
});

test("setJobId updates job ID for current recipe", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId = INITIAL_RECIPE_ID;
await result.current.selectRecipe(recipeId);

const jobId = "job-123";

act(() => {
result.current.setJobId(jobId);
});

expect(result.current.packingResults[recipeId].jobId).toBe(jobId);

for (const recipeId of Object.keys(result.current.inputOptions)) {
if (recipeId !== INITIAL_RECIPE_ID) {
// No other job ids should have been updated to `jobId`
expect(result.current.packingResults[recipeId]?.jobId).not.toBe(jobId);
}
}
});

test("setPackingResults updates packing results for current recipe", async () => {
const { result } = renderHook(() => useRecipeStore());

const recipeId = INITIAL_RECIPE_ID;
await result.current.selectRecipe(recipeId);

const packingResult = {
jobId: "job-456",
jobLogs: "Packing completed successfully.",
resultUrl: "http://example.com/result",
runTime: 120,
outputDir: "/output/dir",
};

act(() => {
result.current.setPackingResults(packingResult);
});

expect(result.current.packingResults[recipeId]).toEqual(packingResult);

for (const recipeId of Object.keys(result.current.inputOptions)) {
if (recipeId !== INITIAL_RECIPE_ID) {
// No other packing results should have been updated to `packingResult`
expect(result.current.packingResults[recipeId]).not.toEqual(packingResult);
}
}
});
1 change: 1 addition & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export default defineConfig({
provider: 'v8',
reporter: ['text', 'json-summary', 'json'],
},
environment: 'jsdom',
},
})