Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1321 from interval/input-slider
Browse files Browse the repository at this point in the history
Add `io.input.slider`
  • Loading branch information
jacobmischka authored Jul 13, 2023
2 parents 94dec79 + 61678d7 commit ea4a8f9
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "interval-sdk"
version = "1.4.4"
version = "1.5.0-dev0"
description = "The frontendless framework for high growth companies. Interval automatically generates apps by inlining the UI in your backend code. It's a faster and more maintainable way to build internal tools, rapid prototypes, and more."
authors = [
"Jacob Mischka <[email protected]>",
Expand Down
15 changes: 15 additions & 0 deletions src/demos/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@ async def hello_interval():
return {"hello": "from python!"}


@interval.action
async def default_slider(io: IO):
nint = await io.input.slider("Ints", min=0, max=10)
nfloat = await io.input.slider("Floats", min=0.0, max=1.0, step=0.1)
ndefault = await io.input.slider("Default", min=0, max=10, default_value=2.0)

print(nint, nfloat, ndefault)

return {
"nint": nint,
"nfloat": nfloat,
"ndefault": ndefault,
}


async def manual_action_handler(io: IO):
await io.display.markdown("IO works!")

Expand Down
67 changes: 67 additions & 0 deletions src/interval_sdk/classes/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
SelectTableState,
InputEmailProps,
InputNumberProps,
InputSliderProps,
InputBooleanProps,
InputRichTextProps,
InputUrlProps,
Expand Down Expand Up @@ -279,6 +280,72 @@ def get_value(val: float):

return InputIOPromise(c, renderer=self._renderer, get_value=get_value)

@overload
def slider(
self,
label: str,
*,
min: int,
max: int,
step: Optional[int] = None,
help_text: Optional[str] = None,
default_value: Optional[int] = None,
disabled: Optional[bool] = None,
) -> InputIOPromise[Literal["INPUT_SLIDER"], int]:
...

@overload
def slider(
self,
label: str,
*,
min: Union[int, float],
max: Union[int, float],
step: Optional[Union[int, float]] = None,
help_text: Optional[str] = None,
default_value: Optional[Union[int, float]] = None,
disabled: Optional[bool] = None,
) -> InputIOPromise[Literal["INPUT_SLIDER"], float]:
...

def slider(
self,
label: str,
*,
min: Union[float, int],
max: Union[float, int],
step: Optional[Union[float, int]] = None,
help_text: Optional[str] = None,
default_value: Optional[Union[float, int]] = None,
disabled: Optional[bool] = None,
):
c = Component(
method_name="INPUT_SLIDER",
label=label,
initial_props=InputSliderProps(
min=min,
max=max,
step=step,
help_text=help_text,
default_value=default_value,
disabled=disabled,
),
display_resolves_immediately=self._display_resolves_immediately,
)

def get_value(val: float):
if (
isinstance(min, int)
and isinstance(max, int)
and (step is None or isinstance(step, int))
and (default_value is None or isinstance(default_value, int))
):
return int(val)

return val

return InputIOPromise(c, renderer=self._renderer, get_value=get_value)

def boolean(
self,
label: str,
Expand Down
15 changes: 15 additions & 0 deletions src/interval_sdk/io_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"INPUT_TEXT",
"INPUT_EMAIL",
"INPUT_NUMBER",
"INPUT_SLIDER",
"INPUT_BOOLEAN",
"INPUT_RICH_TEXT",
"INPUT_SPREADSHEET",
Expand Down Expand Up @@ -531,6 +532,15 @@ class InputNumberProps(BaseModel):
disabled: Optional[bool]


class InputSliderProps(BaseModel):
min: Union[float, int]
max: Union[float, int]
step: Optional[Union[float, int]]
help_text: Optional[str]
default_value: Optional[Union[float, int]]
disabled: Optional[bool]


class InputBooleanProps(BaseModel):
help_text: Optional[str]
default_value: Optional[str]
Expand Down Expand Up @@ -814,6 +824,11 @@ class SearchState(BaseModel):
state=None,
returns=float,
),
"INPUT_SLIDER": MethodDef(
props=InputSliderProps,
state=None,
returns=float,
),
"INPUT_BOOLEAN": MethodDef(
props=InputBooleanProps,
state=None,
Expand Down
94 changes: 94 additions & 0 deletions src/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,100 @@ async def currency(io: IO):
)


class TestInputSlider:
async def test_input_slider_entry(
self, interval: Interval, page: BrowserPage, transactions: Transaction
):
@interval.action
async def input_slider_entry(io: IO):
num1 = await io.input.slider(
"Enter a number between 1-100",
min=1,
max=100,
)

decimal = await io.input.slider(
"Select a decimal value",
min=num1,
max=num1 + 1,
step=0.1,
)

return {"num1": num1, "decimal": decimal, "sum": num1 + decimal}

await transactions.console()
await transactions.run("input_slider_entry")

await transactions.press_continue()
await transactions.expect_validation_error()

await page.locator('input[type="range"]').last.fill("12")
await transactions.press_continue()

await expect(page.locator('text="Select a decimal value"')).to_be_visible()
await page.locator('input[type="range"]').last.fill("12.5")
await transactions.press_continue()
await transactions.expect_success()

async def test_input_slider_keyboard_entry(
self, interval: Interval, page: BrowserPage, transactions: Transaction
):
@interval.action
async def input_slider_keyboard_entry(io: IO):
num1 = await io.input.slider(
"Enter a number between 1-100",
min=1,
max=100,
)

decimal = await io.input.slider(
"Select a decimal value",
min=num1,
max=num1 + 1,
step=0.1,
)

return {"num1": num1, "decimal": decimal, "sum": num1 + decimal}

def last_range():
return page.locator('input[type="range"]').last

await transactions.console()
await transactions.run("input_slider_keyboard_entry")

await transactions.press_continue()
await transactions.expect_validation_error()

# each keystroke should be treated as a separate input, the threshold is 1.5s
await last_range().focus()
await last_range().type(
"79", delay=2000
) # intentional delay to test functionality

assert await last_range().input_value() == "9"

await transactions.press_continue()

await expect(page.locator('text="Select a decimal value"')).to_be_visible()

# this should be treated as a single input, "15"
await last_range().focus()
await last_range().type(
"15", delay=1000
) # intentional delay to test functionality
await transactions.press_continue()
await transactions.expect_validation_error(
"Please enter a number between 9 and 10."
)

await last_range().focus()
await last_range().type("9.5")
await transactions.press_continue()
await transactions.expect_success()

# Not currently implementing Input entry test, it mainly tests the UI and waitForFunction is confusing in python


async def test_input_rich_text(
interval: Interval, page: BrowserPage, transactions: Transaction
):
Expand Down

0 comments on commit ea4a8f9

Please sign in to comment.