Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion frontend/islands/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ OUT_DIR=$(pwd)/dist
echo "validating $OUT_DIR"

echo "[validate: no process.env. variables in any of the js files]"
grep -R "process.env." $(pwd)/dist
grep -R "process\.env\." $(pwd)/dist
if [ $? -eq 0 ]; then
echo "process.env. variables found in js files"
exit 1
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/hooks/__tests__/useDebounce.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe("useDebounceControlledState", () => {
vi.useRealTimers();
});

it("should call onChange with initial value immediately", () => {
it("should not call onChange with initial value immediately", () => {
const onChange = vi.fn();
renderHook(() =>
useDebounceControlledState({
Expand All @@ -78,7 +78,7 @@ describe("useDebounceControlledState", () => {
}),
);

expect(onChange).toHaveBeenCalledWith("initial");
expect(onChange).not.toHaveBeenCalled();
});

it("should debounce subsequent onChange calls", () => {
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,10 @@ export function useDebounceControlledState<T>(opts: {

const onUpdate = useEvent(onChange);

// If the initialValue changes, update the internal value and trigger onChange immediately
// Handle external changes:
useEffect(() => {
setInternalValue(initialValue);
// When initialValue changes (like during reset), update immediately
if (!disabled) {
onUpdate(initialValue);
}
}, [initialValue, disabled, onUpdate]);
}, [initialValue]);

// Handle debounced updates for user input
useEffect(() => {
Expand All @@ -64,7 +60,7 @@ export function useDebounceControlledState<T>(opts: {
if (debouncedValue !== initialValue) {
onUpdate(debouncedValue);
}
}, [debouncedValue, initialValue, disabled, onUpdate]);
}, [debouncedValue, disabled, onUpdate]);

// If disabled, just pass through the initialValue and onChange
if (disabled) {
Expand Down
82 changes: 1 addition & 81 deletions frontend/src/plugins/impl/__tests__/NumberPlugin.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Copyright 2024 Marimo. All rights reserved. */

import { act, fireEvent, render } from "@testing-library/react";
import { act, render } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { IPluginProps } from "../../types";
import { NumberPlugin } from "../NumberPlugin";
Expand Down Expand Up @@ -61,84 +61,4 @@ describe("NumberPlugin", () => {
rerender(plugin.render(resetProps));
expect(input.value).toBe("0");
});

it("handles both immediate prop changes and debounced user input", () => {
const plugin = new NumberPlugin();
const host = document.createElement("div");
const setValue = vi.fn();

const props: IPluginProps<
number | null,
(typeof plugin)["validator"]["_type"]
> = {
host,
value: 5,
setValue,
data: {
start: 0,
stop: 10,
step: 1,
label: null,
debounce: true,
fullWidth: false,
},
functions: {},
};

// Initial render - setValue should be called immediately with initial value
const { getByRole, rerender } = render(plugin.render(props));

// Wait for React-Aria NumberField to initialize
act(() => {
vi.advanceTimersByTime(0);
});

expect(setValue).toHaveBeenCalledWith(5);

// Clear the mock to test debounced user input
setValue.mockClear();

const input = getByRole("textbox", {
name: "Number input",
}) as HTMLInputElement;
expect(input).toBeTruthy();

// Simulate user typing and committing the value
act(() => {
// Focus and type the value
fireEvent.focus(input);
// Simulate React-Aria NumberField value change
fireEvent.change(input, { target: { value: "7" } });
// Commit the value with Enter key
fireEvent.keyDown(input, { key: "Enter" });
});

// Let React process the input
act(() => {
vi.advanceTimersByTime(0);
});

// Commit the value
act(() => {
fireEvent.blur(input);
});

// Process debounced updates
act(() => {
vi.advanceTimersByTime(200);
});

// Should call setValue after debounce for user input
expect(setValue).toHaveBeenCalledWith(7);

// Clear the mock again to test immediate prop changes
setValue.mockClear();

// Update props - should trigger immediate setValue
const updatedProps = { ...props, value: 3 };
rerender(plugin.render(updatedProps));
expect(setValue).toHaveBeenCalledWith(3);

vi.useRealTimers();
});
});
Loading