|
16 | 16 | */ |
17 | 17 |
|
18 | 18 | import { describe, expect, it, vi } from "vitest"; |
19 | | -import { render, screen, fireEvent } from "@testing-library/react"; |
| 19 | +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; |
20 | 20 | import Toolbar, { applyDragMove } from "./Toolbar"; |
21 | 21 | import { PivotData, type DataRecord } from "../engine/PivotData"; |
22 | 22 | import { makeConfig } from "../test-utils"; |
23 | 23 |
|
24 | 24 | const ALL_COLUMNS = ["region", "year", "revenue", "profit", "category"]; |
25 | 25 | const NUMERIC_COLUMNS = ["revenue", "profit"]; |
| 26 | +const MANY_FIELDS = [ |
| 27 | + "region", |
| 28 | + "year", |
| 29 | + "category", |
| 30 | + "segment", |
| 31 | + "state", |
| 32 | + "city", |
| 33 | + "country", |
| 34 | + "quarter", |
| 35 | + "month", |
| 36 | + "channel", |
| 37 | +]; |
| 38 | +const MANY_NUMERIC_COLUMNS = [ |
| 39 | + "revenue", |
| 40 | + "profit", |
| 41 | + "cost", |
| 42 | + "margin", |
| 43 | + "units", |
| 44 | + "discount", |
| 45 | + "tax", |
| 46 | + "freight", |
| 47 | + "returns", |
| 48 | +]; |
26 | 49 |
|
27 | 50 | describe("Toolbar - rendering", () => { |
28 | 51 | it("renders toolbar with data-testid", () => { |
@@ -466,6 +489,162 @@ describe("Toolbar - interactions", () => { |
466 | 489 | }); |
467 | 490 | }); |
468 | 491 |
|
| 492 | +describe("Toolbar - field search", () => { |
| 493 | + it("does not render search input when options are at or below the threshold", () => { |
| 494 | + render( |
| 495 | + <Toolbar |
| 496 | + config={makeConfig({ rows: [] })} |
| 497 | + allColumns={ALL_COLUMNS} |
| 498 | + numericColumns={NUMERIC_COLUMNS} |
| 499 | + onConfigChange={vi.fn()} |
| 500 | + />, |
| 501 | + ); |
| 502 | + |
| 503 | + fireEvent.click(screen.getByTestId("toolbar-rows-select")); |
| 504 | + expect(screen.queryByTestId("toolbar-rows-search")).not.toBeInTheDocument(); |
| 505 | + }); |
| 506 | + |
| 507 | + it("renders search input above threshold and filters options", () => { |
| 508 | + render( |
| 509 | + <Toolbar |
| 510 | + config={makeConfig({ rows: [] })} |
| 511 | + allColumns={MANY_FIELDS} |
| 512 | + numericColumns={NUMERIC_COLUMNS} |
| 513 | + onConfigChange={vi.fn()} |
| 514 | + />, |
| 515 | + ); |
| 516 | + |
| 517 | + fireEvent.click(screen.getByTestId("toolbar-rows-select")); |
| 518 | + const search = screen.getByTestId("toolbar-rows-search"); |
| 519 | + expect(search).toBeInTheDocument(); |
| 520 | + |
| 521 | + fireEvent.change(search, { target: { value: "cat" } }); |
| 522 | + expect( |
| 523 | + screen.getByTestId("toolbar-rows-option-category"), |
| 524 | + ).toBeInTheDocument(); |
| 525 | + expect( |
| 526 | + screen.queryByTestId("toolbar-rows-option-region"), |
| 527 | + ).not.toBeInTheDocument(); |
| 528 | + |
| 529 | + fireEvent.change(search, { target: { value: "" } }); |
| 530 | + expect( |
| 531 | + screen.getByTestId("toolbar-rows-option-region"), |
| 532 | + ).toBeInTheDocument(); |
| 533 | + }); |
| 534 | + |
| 535 | + it("does not move focus into the search input when the dropdown opens", () => { |
| 536 | + render( |
| 537 | + <Toolbar |
| 538 | + config={makeConfig({ rows: [] })} |
| 539 | + allColumns={MANY_FIELDS} |
| 540 | + numericColumns={NUMERIC_COLUMNS} |
| 541 | + onConfigChange={vi.fn()} |
| 542 | + />, |
| 543 | + ); |
| 544 | + |
| 545 | + const trigger = screen.getByTestId("toolbar-rows-select"); |
| 546 | + trigger.focus(); |
| 547 | + fireEvent.click(trigger); |
| 548 | + const search = screen.getByTestId("toolbar-rows-search"); |
| 549 | + |
| 550 | + expect(trigger).toHaveFocus(); |
| 551 | + expect(search).not.toHaveFocus(); |
| 552 | + }); |
| 553 | + |
| 554 | + it("closes the dropdown on Escape even when search yields zero results", () => { |
| 555 | + render( |
| 556 | + <Toolbar |
| 557 | + config={makeConfig({ rows: [] })} |
| 558 | + allColumns={MANY_FIELDS} |
| 559 | + numericColumns={NUMERIC_COLUMNS} |
| 560 | + onConfigChange={vi.fn()} |
| 561 | + />, |
| 562 | + ); |
| 563 | + |
| 564 | + fireEvent.click(screen.getByTestId("toolbar-rows-select")); |
| 565 | + const search = screen.getByTestId("toolbar-rows-search"); |
| 566 | + fireEvent.change(search, { target: { value: "zzz" } }); |
| 567 | + |
| 568 | + expect( |
| 569 | + screen.getByTestId("toolbar-rows-no-search-results"), |
| 570 | + ).toBeInTheDocument(); |
| 571 | + fireEvent.keyDown(search, { key: "Escape" }); |
| 572 | + expect(screen.queryByTestId("toolbar-rows-panel")).not.toBeInTheDocument(); |
| 573 | + }); |
| 574 | + |
| 575 | + it("moves focus to the first option on ArrowDown from the search input", () => { |
| 576 | + render( |
| 577 | + <Toolbar |
| 578 | + config={makeConfig({ rows: [] })} |
| 579 | + allColumns={MANY_FIELDS} |
| 580 | + numericColumns={NUMERIC_COLUMNS} |
| 581 | + onConfigChange={vi.fn()} |
| 582 | + />, |
| 583 | + ); |
| 584 | + |
| 585 | + fireEvent.click(screen.getByTestId("toolbar-rows-select")); |
| 586 | + const search = screen.getByTestId("toolbar-rows-search"); |
| 587 | + const firstOption = screen.getByTestId("toolbar-rows-option-region"); |
| 588 | + |
| 589 | + search.focus(); |
| 590 | + fireEvent.keyDown(search, { key: "ArrowDown" }); |
| 591 | + expect(firstOption).toHaveFocus(); |
| 592 | + }); |
| 593 | + |
| 594 | + it("filters value-field options without hiding synthetic or aggregation controls", () => { |
| 595 | + render( |
| 596 | + <Toolbar |
| 597 | + config={makeConfig({ values: ["revenue", "profit"] })} |
| 598 | + allColumns={[...MANY_FIELDS, ...MANY_NUMERIC_COLUMNS]} |
| 599 | + numericColumns={MANY_NUMERIC_COLUMNS} |
| 600 | + onConfigChange={vi.fn()} |
| 601 | + />, |
| 602 | + ); |
| 603 | + |
| 604 | + fireEvent.click(screen.getByTestId("toolbar-values-select")); |
| 605 | + const search = screen.getByTestId("toolbar-values-search"); |
| 606 | + fireEvent.change(search, { target: { value: "tax" } }); |
| 607 | + |
| 608 | + expect(screen.getByTestId("toolbar-values-option-tax")).toBeInTheDocument(); |
| 609 | + expect( |
| 610 | + screen.queryByTestId("toolbar-values-option-revenue"), |
| 611 | + ).not.toBeInTheDocument(); |
| 612 | + expect( |
| 613 | + screen.getByTestId("toolbar-values-add-synthetic"), |
| 614 | + ).toBeInTheDocument(); |
| 615 | + expect( |
| 616 | + screen.getByTestId("toolbar-values-aggregation-controls"), |
| 617 | + ).toBeInTheDocument(); |
| 618 | + }); |
| 619 | + |
| 620 | + it("clears the values search when opening the synthetic measure builder", () => { |
| 621 | + render( |
| 622 | + <Toolbar |
| 623 | + config={makeConfig({ values: ["revenue", "profit"] })} |
| 624 | + allColumns={[...MANY_FIELDS, ...MANY_NUMERIC_COLUMNS]} |
| 625 | + numericColumns={MANY_NUMERIC_COLUMNS} |
| 626 | + onConfigChange={vi.fn()} |
| 627 | + />, |
| 628 | + ); |
| 629 | + |
| 630 | + fireEvent.click(screen.getByTestId("toolbar-values-select")); |
| 631 | + fireEvent.change(screen.getByTestId("toolbar-values-search"), { |
| 632 | + target: { value: "tax" }, |
| 633 | + }); |
| 634 | + fireEvent.click(screen.getByTestId("toolbar-values-add-synthetic")); |
| 635 | + |
| 636 | + expect( |
| 637 | + screen.queryByTestId("toolbar-values-panel"), |
| 638 | + ).not.toBeInTheDocument(); |
| 639 | + |
| 640 | + fireEvent.click(screen.getByTestId("toolbar-values-select")); |
| 641 | + expect(screen.getByTestId("toolbar-values-search")).toHaveValue(""); |
| 642 | + expect( |
| 643 | + screen.getByTestId("toolbar-values-option-revenue"), |
| 644 | + ).toBeInTheDocument(); |
| 645 | + }); |
| 646 | +}); |
| 647 | + |
469 | 648 | describe("Toolbar - reset", () => { |
470 | 649 | it("shows reset button when config differs from initial", () => { |
471 | 650 | const initial = makeConfig({ aggregation: "sum" }); |
|
0 commit comments