langflow/src/frontend/tests/core/unit/tabComponent.spec.ts
Lucas Oliveira 95ceb52fa3
feat: add new Tab input (#7032)
* Added TabMixin

* Added Tab component on inputs

* Added Tab component to initializations

* Added tests for tab input

* Added Tab Component type

* Added options and active tab to input field type

* Added tab component on frontend

* Instantiate tab component

* Update package lock

* Refactor input classes and imports for consistency

- Reordered imports to maintain consistency across files.
- Simplified class definitions by removing unnecessary line breaks.
- Updated the `__all__` list in `__init__.py` files to include `TableInput` consistently.
- Adjusted test cases for cleaner syntax without altering functionality.

* Add constants for tab options limits in input mixin

- Introduced `MAX_TAB_OPTIONS` and `MAX_TAB_OPTION_LENGTH` constants for better maintainability.
- Updated validation logic in `TabMixin` to use these constants for clearer and more flexible error messages.

* Refactor tab input validation tests for improved clarity

- Replaced individual test cases for invalid tab inputs with a parameterized test function.
- Enhanced test coverage by including cases for too many options, exceeding character limits, and non-string values.
- Improved documentation within the test function for better understanding of validation scenarios.

* Enhance tab input validation tests with parameterization

- Refactored `test_tab_input_valid` to use `pytest.mark.parametrize` for improved test coverage and clarity.
- Included multiple scenarios for valid tab inputs, such as standard, fewer options, and empty options.
- Updated assertions to reflect the expected outcomes based on parameterized inputs.

* Enhance TabInput validation to ensure value is a string and one of the specified options

- Updated the `validate_value` method to enforce that the input value is a string.
- Added a check to validate that the value is among the allowed options, raising a ValueError with a descriptive message if not.
- Improved error handling for better user feedback on invalid inputs.

* Fix optional chaining in error handling within CodeAreaModal

- Updated the error check in the `delayedFunction` to use optional chaining for safer access to the error detail.
- This change ensures that the code handles cases where `detail` may be undefined, improving robustness.

* Add 'TAB' field type to schema and update direct types list

- Included 'TAB' as a valid field type in the schema conversion dictionary.
- Updated the DIRECT_TYPES list to include 'tab', ensuring consistency across type definitions.
- These changes enhance the flexibility of the input handling for tab components.

* Add unit test

* Re-added noqa

* fix: unit tests

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-03-17 14:10:48 +00:00

164 lines
4.7 KiB
TypeScript

import { expect, Page, test } from "@playwright/test";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
test(
"user should interact with tab component",
{ tag: ["@release", "@workspace"] },
async ({ context, page }) => {
await awaitBootstrapTest(page);
await page.waitForSelector('[data-testid="blank-flow"]', {
timeout: 30000,
});
await page.getByTestId("blank-flow").click();
await page.waitForSelector(
'[data-testid="sidebar-custom-component-button"]',
{
timeout: 3000,
},
);
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTestId("title-Custom Component").first().click();
await page.waitForSelector('[data-testid="code-button-modal"]', {
timeout: 3000,
});
await page.getByTestId("code-button-modal").click();
let cleanCode = await extractAndCleanCode(page);
// Use regex pattern to match the imports section more flexibly
cleanCode = updateComponentCode(cleanCode, {
imports: ["MessageTextInput", "Output", "TabInput"],
inputs: [
{
name: "MessageTextInput",
config: {
name: "input_value",
display_name: "Input Value",
info: "This is a custom component Input",
value: "Hello, World!",
tool_mode: true,
},
},
{
name: "TabInput",
config: {
name: "tab_selection",
display_name: "Tab Selection",
options: ["Tab 1", "Tab 2", "Tab 3"],
value: "Tab 1",
},
},
],
});
await page.locator("textarea").last().press(`ControlOrMeta+a`);
await page.keyboard.press("Backspace");
await page.locator("textarea").last().fill(cleanCode);
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page.waitForSelector('[data-testid="fit_view"]', {
timeout: 3000,
});
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
// Verify that all tabs are visible
expect(await page.getByText("Tab 1").isVisible()).toBeTruthy();
expect(await page.getByText("Tab 2").isVisible()).toBeTruthy();
expect(await page.getByText("Tab 3").isVisible()).toBeTruthy();
// Verify that Tab 1 is active by default (as specified in the value)
expect(
await page
.getByRole("tab", { name: "Tab 1", selected: true })
.isVisible(),
).toBeTruthy();
// Click on Tab 2 and verify it becomes active
await page.getByRole("tab", { name: "Tab 2" }).click();
expect(
await page
.getByRole("tab", { name: "Tab 2", selected: true })
.isVisible(),
).toBeTruthy();
// Click on Tab 3 and verify it becomes active
await page.getByRole("tab", { name: "Tab 3" }).click();
expect(
await page
.getByRole("tab", { name: "Tab 3", selected: true })
.isVisible(),
).toBeTruthy();
},
);
async function extractAndCleanCode(page: Page): Promise<string> {
const outerHTML = await page
.locator('//*[@id="codeValue"]')
.evaluate((el) => el.outerHTML);
const valueMatch = outerHTML.match(/value="([\s\S]*?)"/);
if (!valueMatch) {
throw new Error("Could not find value attribute in the HTML");
}
let codeContent = valueMatch[1]
.replace(/&quot;/g, '"')
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&#x27;/g, "'")
.replace(/&#x2F;/g, "/");
return codeContent;
}
function updateComponentCode(
code: string,
updates: {
imports?: string[];
inputs?: Array<{ name: string; config: Record<string, any> }>;
},
): string {
let updatedCode = code;
// Update imports
if (updates.imports) {
const importPattern = /from\s+langflow\.io\s+import\s+([^;\n]+)/;
const newImports = updates.imports.join(", ");
updatedCode = updatedCode.replace(
importPattern,
`from langflow.io import ${newImports}`,
);
}
// Update inputs
if (updates.inputs) {
const inputsPattern = /inputs\s*=\s*\[([\s\S]*?)\]/;
const newInputs = updates.inputs
.map(({ name, config }) => {
const params = Object.entries(config)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join(",\n ");
return ` ${name}(\n ${params}\n )`;
})
.join(",\n");
updatedCode = updatedCode.replace(
inputsPattern,
`inputs = [\n${newInputs}\n ]`,
);
updatedCode = updatedCode.replace("true", "True");
updatedCode = updatedCode.replace("false", "False");
}
return updatedCode;
}