This is page 177 of 179. Use http://codebase.md/xmlui-org/xmlui/xmlui/mockApiDef.js?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── cold-items-taste.md │ ├── config.json │ ├── empty-spiders-dress.md │ ├── shy-windows-allow.md │ ├── sour-coins-read.md │ ├── tame-zebras-invite.md │ ├── three-ideas-invent.md │ ├── twenty-jeans-watch.md │ ├── warm-spies-melt.md │ └── whole-ways-cry.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── actions.md │ ├── AppRoot.md │ ├── component-apis.md │ ├── component-rendering.md │ ├── component-review-checklist.md │ ├── containers.md │ ├── data-sources.md │ ├── e2e-summary.md │ ├── expression-evaluation.md │ ├── glossary.md │ ├── helper-components.md │ ├── index.md │ ├── loaders.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── rendering-fundamentals.md │ ├── reusable-components.md │ ├── standalone-apps.md │ ├── state-management.md │ └── xmlui-extensibility.xlsx ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ ├── BehaviorContext.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/src/components/Tree/Tree-dynamic.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { expect, test } from "../../testing/fixtures"; 2 | import { 3 | flatTreeData, 4 | hierarchyTreeData, 5 | dynamicTreeData, 6 | customDynamicTreeData, 7 | dynamicFlatData, 8 | } from "./testData"; 9 | 10 | // ============================================================================= 11 | // IMPERATIVE API TESTS 12 | // ============================================================================= 13 | 14 | test.describe("Imperative API", () => { 15 | test("exposes expandNode method", async ({ 16 | initTestBed, 17 | createTreeDriver, 18 | createButtonDriver, 19 | }) => { 20 | await initTestBed(` 21 | <Fragment> 22 | <VStack height="400px"> 23 | <Tree id="treeApi" testId="tree" 24 | dataFormat="flat" 25 | data='{${JSON.stringify(flatTreeData)}}'> 26 | <property name="itemTemplate"> 27 | <HStack testId="{$item.id}:expand" verticalAlignment="center"> 28 | <Text value="{$item.name}" /> 29 | </HStack> 30 | </property> 31 | </Tree> 32 | </VStack> 33 | <Button id="expandBtn" testId="expand-node-btn" label="Expand Node 1" 34 | onClick="treeApi.expandNode(1);" /> 35 | </Fragment> 36 | `); 37 | 38 | const tree = await createTreeDriver("tree"); 39 | const expandButton = await createButtonDriver("expand-node-btn"); 40 | 41 | // Initially, tree should be collapsed 42 | await expect(tree.getByTestId("1:expand")).toBeVisible(); // Root visible 43 | await expect(tree.getByTestId("2:expand")).not.toBeVisible(); // Child hidden 44 | await expect(tree.getByTestId("3:expand")).not.toBeVisible(); // Child hidden 45 | 46 | // Click expand specific node button 47 | await expandButton.click(); 48 | 49 | // Verify node 1's children are now visible but grandchildren are still hidden 50 | await expect(tree.getByTestId("1:expand")).toBeVisible(); // Root visible 51 | await expect(tree.getByTestId("2:expand")).toBeVisible(); // Child now visible 52 | await expect(tree.getByTestId("3:expand")).toBeVisible(); // Child now visible 53 | await expect(tree.getByTestId("4:expand")).not.toBeVisible(); // Grandchild still hidden (node 2 not expanded) 54 | }); 55 | 56 | test("exposes collapseNode method", async ({ 57 | initTestBed, 58 | createTreeDriver, 59 | createButtonDriver, 60 | }) => { 61 | await initTestBed(` 62 | <Fragment> 63 | <VStack height="400px"> 64 | <Tree id="treeApi" testId="tree" 65 | dataFormat="flat" 66 | defaultExpanded="all" 67 | data='{${JSON.stringify(flatTreeData)}}'> 68 | <property name="itemTemplate"> 69 | <HStack testId="{$item.id}:expand" verticalAlignment="center"> 70 | <Text value="{$item.name}" /> 71 | </HStack> 72 | </property> 73 | </Tree> 74 | </VStack> 75 | <Button id="collapseBtn" testId="collapse-btn" label="Collapse Node 1" 76 | onClick="treeApi.collapseNode(1);" /> 77 | </Fragment> 78 | `); 79 | 80 | const tree = await createTreeDriver("tree"); 81 | const collapseButton = await createButtonDriver("collapse-btn"); 82 | 83 | // Verify tree starts with all nodes visible (defaultExpanded="all") 84 | await expect(tree.getByTestId("1:expand")).toBeVisible(); 85 | await expect(tree.getByTestId("2:expand")).toBeVisible(); 86 | await expect(tree.getByTestId("3:expand")).toBeVisible(); 87 | await expect(tree.getByTestId("4:expand")).toBeVisible(); 88 | 89 | // Click collapse node button 90 | await collapseButton.click(); 91 | 92 | // Verify node 1 children are now hidden 93 | await expect(tree.getByTestId("1:expand")).toBeVisible(); // Root still visible 94 | await expect(tree.getByTestId("2:expand")).not.toBeVisible(); // Child hidden 95 | await expect(tree.getByTestId("3:expand")).not.toBeVisible(); // Child hidden 96 | await expect(tree.getByTestId("4:expand")).not.toBeVisible(); // Grandchild hidden 97 | }); 98 | 99 | test("exposes expandAll method", async ({ 100 | initTestBed, 101 | createTreeDriver, 102 | createButtonDriver, 103 | }) => { 104 | await initTestBed(` 105 | <Fragment> 106 | <VStack height="400px"> 107 | <Tree id="treeApi" testId="tree" 108 | dataFormat="flat" 109 | data='{${JSON.stringify(flatTreeData)}}'> 110 | <property name="itemTemplate"> 111 | <HStack testId="{$item.id}:expandall" verticalAlignment="center"> 112 | <Text value="{$item.name}" /> 113 | </HStack> 114 | </property> 115 | </Tree> 116 | </VStack> 117 | <Button id="expandBtn" testId="expand-all-btn" label="Expand All" 118 | onClick="treeApi.expandAll();" /> 119 | </Fragment> 120 | `); 121 | 122 | const tree = await createTreeDriver("tree"); 123 | const expandButton = await createButtonDriver("expand-all-btn"); 124 | 125 | // Initially, tree should be collapsed (not expanded) 126 | await expect(tree.getByTestId("2:expandall")).not.toBeVisible(); 127 | await expect(tree.getByTestId("4:expandall")).not.toBeVisible(); 128 | 129 | // Click expandAll button 130 | await expandButton.click(); 131 | 132 | // Verify all nodes are now visible (expanded) 133 | await expect(tree.getByTestId("1:expandall")).toBeVisible(); // Root 134 | await expect(tree.getByTestId("2:expandall")).toBeVisible(); // Child 135 | await expect(tree.getByTestId("3:expandall")).toBeVisible(); // Child 136 | await expect(tree.getByTestId("4:expandall")).toBeVisible(); // Grandchild 137 | }); 138 | 139 | test("exposes collapseAll method", async ({ 140 | initTestBed, 141 | createTreeDriver, 142 | createButtonDriver, 143 | }) => { 144 | await initTestBed(` 145 | <Fragment> 146 | <VStack height="400px"> 147 | <Tree id="treeApi" testId="tree" 148 | dataFormat="flat" 149 | defaultExpanded="all" 150 | data='{${JSON.stringify(flatTreeData)}}'> 151 | <property name="itemTemplate"> 152 | <HStack testId="{$item.id}:collapseall" verticalAlignment="center"> 153 | <Text value="{$item.name}" /> 154 | </HStack> 155 | </property> 156 | </Tree> 157 | </VStack> 158 | <Button id="collapseBtn" testId="collapse-all-btn" label="Collapse All" 159 | onClick="treeApi.collapseAll();" /> 160 | </Fragment> 161 | `); 162 | 163 | const tree = await createTreeDriver("tree"); 164 | const collapseButton = await createButtonDriver("collapse-all-btn"); 165 | 166 | // Initially, tree should be fully expanded (defaultExpanded="all") 167 | await expect(tree.getByTestId("1:collapseall")).toBeVisible(); // Root 168 | await expect(tree.getByTestId("2:collapseall")).toBeVisible(); // Child 169 | await expect(tree.getByTestId("3:collapseall")).toBeVisible(); // Child 170 | await expect(tree.getByTestId("4:collapseall")).toBeVisible(); // Grandchild 171 | 172 | // Click collapseAll button 173 | await collapseButton.click(); 174 | 175 | // Verify only root nodes are visible (all collapsed) 176 | await expect(tree.getByTestId("1:collapseall")).toBeVisible(); // Root still visible 177 | await expect(tree.getByTestId("2:collapseall")).not.toBeVisible(); // Child hidden 178 | await expect(tree.getByTestId("3:collapseall")).not.toBeVisible(); // Child hidden 179 | await expect(tree.getByTestId("4:collapseall")).not.toBeVisible(); // Grandchild hidden 180 | }); 181 | 182 | test("exposes scrollToItem method", async ({ 183 | initTestBed, 184 | createTreeDriver, 185 | createButtonDriver, 186 | }) => { 187 | // Create a larger dataset to ensure scrolling is needed 188 | const largeTreeData = [ 189 | { id: 1, name: "Root Item 1", parentId: null }, 190 | { id: 2, name: "Child Item 1.1", parentId: 1 }, 191 | { id: 3, name: "Child Item 1.2", parentId: 1 }, 192 | { id: 4, name: "Grandchild Item 1.1.1", parentId: 2 }, 193 | { id: 5, name: "Grandchild Item 1.1.2", parentId: 2 }, 194 | { id: 6, name: "Root Item 2", parentId: null }, 195 | { id: 7, name: "Child Item 2.1", parentId: 6 }, 196 | { id: 8, name: "Child Item 2.2", parentId: 6 }, 197 | { id: 9, name: "Child Item 2.3", parentId: 6 }, 198 | { id: 10, name: "Root Item 3", parentId: null }, 199 | { id: 11, name: "Child Item 3.1", parentId: 10 }, 200 | { id: 12, name: "Child Item 3.2", parentId: 10 }, 201 | { id: 13, name: "Root Item 4", parentId: null }, 202 | { id: 14, name: "Child Item 4.1", parentId: 13 }, 203 | { id: 15, name: "Child Item 4.2", parentId: 13 }, 204 | { id: 16, name: "Root Item 5", parentId: null }, 205 | { id: 17, name: "Child Item 5.1", parentId: 16 }, 206 | { id: 18, name: "Child Item 5.2", parentId: 16 }, 207 | { id: 19, name: "Target Item (Bottom)", parentId: 16 }, // This will be at the bottom 208 | ]; 209 | 210 | const { testStateDriver } = await initTestBed(` 211 | <Fragment> 212 | <VStack height="150px"> 213 | <Tree id="treeApi" testId="tree" 214 | dataFormat="flat" 215 | parentIdField="parentId" 216 | defaultExpanded="all" 217 | data='{${JSON.stringify(largeTreeData)}}'> 218 | <property name="itemTemplate"> 219 | <HStack testId="{$item.id}:scroll" verticalAlignment="center"> 220 | <Text value="{$item.name}" /> 221 | </HStack> 222 | </property> 223 | </Tree> 224 | </VStack> 225 | <Button id="scrollBtn" testId="scroll-btn" label="Scroll to Bottom Item" onClick=" 226 | treeApi.scrollToItem('19'); 227 | testState = { actionPerformed: 'scrollToItem', itemId: '19' }; 228 | " /> 229 | </Fragment> 230 | `); 231 | 232 | const tree = await createTreeDriver("tree"); 233 | const scrollButton = await createButtonDriver("scroll-btn"); 234 | 235 | // Verify tree is visible and first items are visible 236 | await expect(tree.getByTestId("1:scroll")).toBeVisible(); 237 | await expect(tree.getByTestId("2:scroll")).toBeVisible(); 238 | 239 | // Verify the target item at the bottom is initially NOT visible in the small viewport 240 | // (Due to the small height of 150px and many items, item 19 should be out of view) 241 | await expect(tree.getByTestId("19:scroll")).not.toBeVisible(); 242 | 243 | // Click scroll to item button 244 | await scrollButton.click(); 245 | 246 | // Verify test state confirms action was performed 247 | await expect.poll(testStateDriver.testState).toEqual({ 248 | actionPerformed: "scrollToItem", 249 | itemId: "19", 250 | }); 251 | 252 | // After scrolling, the target item should now be visible 253 | // Note: We can't easily test the exact scroll position in virtualized components, 254 | // but we can verify the API was called successfully 255 | }); 256 | 257 | test("exposes getSelectedNode method", async ({ 258 | initTestBed, 259 | createTreeDriver, 260 | createButtonDriver, 261 | }) => { 262 | const { testStateDriver } = await initTestBed(` 263 | <Fragment> 264 | <VStack height="400px"> 265 | <Tree id="treeApi" testId="tree" 266 | dataFormat="flat" 267 | defaultExpanded="all" 268 | selectedValue="2" 269 | data='{${JSON.stringify(flatTreeData)}}'> 270 | <property name="itemTemplate"> 271 | <HStack testId="{$item.id}:selected" verticalAlignment="center"> 272 | <Text value="{$item.name}" /> 273 | </HStack> 274 | </property> 275 | </Tree> 276 | </VStack> 277 | <Button id="getSelectedBtn" testId="get-selected-btn" label="Get Selected" onClick=" 278 | const selectedNode = treeApi.getSelectedNode(); 279 | testState = { 280 | hasSelectedNode: selectedNode !== null, 281 | selectedNodeKey: selectedNode?.key, 282 | selectedNodeName: selectedNode?.displayName 283 | }; 284 | " /> 285 | </Fragment> 286 | `); 287 | 288 | const tree = await createTreeDriver("tree"); 289 | const getSelectedButton = await createButtonDriver("get-selected-btn"); 290 | 291 | // Verify tree is visible with selection 292 | await expect(tree.getByTestId("2:selected")).toBeVisible(); 293 | 294 | // Click get selected node button 295 | await getSelectedButton.click(); 296 | 297 | // Verify getSelectedNode returns correct data 298 | await expect.poll(testStateDriver.testState).toEqual({ 299 | hasSelectedNode: true, 300 | selectedNodeKey: 2, 301 | selectedNodeName: "Child Item 1.1", 302 | }); 303 | }); 304 | 305 | test("exposes appendNode method with flat data format #1", async ({ 306 | initTestBed, 307 | createTreeDriver, 308 | createButtonDriver, 309 | }) => { 310 | await initTestBed(` 311 | <Fragment> 312 | <VStack height="400px"> 313 | <Tree id="treeApi" testId="tree" 314 | dataFormat="flat" 315 | data='{${JSON.stringify(flatTreeData)}}'> 316 | <property name="itemTemplate"> 317 | <HStack testId="{$item.id}" verticalAlignment="center"> 318 | <Text value="{$item.name}" /> 319 | </HStack> 320 | </property> 321 | </Tree> 322 | </VStack> 323 | <Button id="appendBtn" testId="append-btn" label="Append Child to Node 1" 324 | onClick="treeApi.appendNode(1, { id: 5, name: 'New Child Item' });" /> 325 | <Button id="expandBtn" testId="expand-btn" label="Expand Node 1" 326 | onClick="treeApi.expandNode(1);" /> 327 | </Fragment> 328 | `); 329 | 330 | const tree = await createTreeDriver("tree"); 331 | const appendButton = await createButtonDriver("append-btn"); 332 | const expandButton = await createButtonDriver("expand-btn"); 333 | 334 | // Check initial tree structure - only root nodes visible 335 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 336 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Child hidden (collapsed) 337 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Child hidden (collapsed) 338 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Grandchild hidden (collapsed) 339 | 340 | // Append new node using API 341 | await appendButton.click(); 342 | 343 | // Expand node 1 to see its children 344 | await expandButton.click(); 345 | 346 | // Verify original children are visible 347 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 348 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 349 | 350 | // Verify new node is now visible as a child of node 1 351 | await expect(tree.getByTestId("5")).toBeVisible(); // New child 352 | }); 353 | 354 | test("exposes appendNode method with flat data format #2", async ({ 355 | initTestBed, 356 | createTreeDriver, 357 | createButtonDriver, 358 | }) => { 359 | await initTestBed(` 360 | <Fragment> 361 | <VStack height="400px"> 362 | <Tree id="treeApi" testId="tree" 363 | dataFormat="flat" 364 | defaultExpanded="all" 365 | data='{${JSON.stringify(flatTreeData)}}'> 366 | <property name="itemTemplate"> 367 | <HStack testId="{$item.id}" verticalAlignment="center"> 368 | <Text value="{$item.name}" /> 369 | </HStack> 370 | </property> 371 | </Tree> 372 | </VStack> 373 | <Button id="appendBtn" testId="append-btn" label="Append new root node" 374 | onClick="treeApi.appendNode(null, { id: 5, name: 'New Root Item' });" /> 375 | </Fragment> 376 | `); 377 | 378 | const tree = await createTreeDriver("tree"); 379 | const appendButton = await createButtonDriver("append-btn"); 380 | 381 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 382 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 383 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 384 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 385 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 386 | 387 | // Append new root node using API 388 | await appendButton.click(); 389 | 390 | // Verify original nodes are still visible 391 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 392 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 393 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 394 | await expect(tree.getByTestId("4")).toBeVisible(); // Original grandchild 395 | 396 | // Verify new root node is now visible 397 | await expect(tree.getByTestId("5")).toBeVisible(); // New root node 398 | }); 399 | 400 | test("exposes appendNode method with hierarchy data format #1", async ({ 401 | initTestBed, 402 | createTreeDriver, 403 | createButtonDriver, 404 | }) => { 405 | await initTestBed(` 406 | <Fragment> 407 | <VStack height="400px"> 408 | <Tree id="treeApi" testId="tree" 409 | dataFormat="hierarchy" 410 | data='{${JSON.stringify(hierarchyTreeData)}}'> 411 | <property name="itemTemplate"> 412 | <HStack testId="{$item.id}" verticalAlignment="center"> 413 | <Text value="{$item.name}" /> 414 | </HStack> 415 | </property> 416 | </Tree> 417 | </VStack> 418 | <Button id="appendBtn" testId="append-btn" label="Append Child to Node 1" 419 | onClick="treeApi.appendNode(1, { id: 5, name: 'New Child Item' });" /> 420 | <Button id="expandBtn" testId="expand-btn" label="Expand Node 1" 421 | onClick="treeApi.expandNode(1);" /> 422 | </Fragment> 423 | `); 424 | 425 | const tree = await createTreeDriver("tree"); 426 | const appendButton = await createButtonDriver("append-btn"); 427 | const expandButton = await createButtonDriver("expand-btn"); 428 | 429 | // Check initial tree structure - only root nodes visible 430 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 431 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Child hidden (collapsed) 432 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Child hidden (collapsed) 433 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Grandchild hidden (collapsed) 434 | 435 | // Append new node using API 436 | await appendButton.click(); 437 | 438 | // Expand node 1 to see its children 439 | await expandButton.click(); 440 | 441 | // Verify original children are visible 442 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 443 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 444 | 445 | // Verify new node is now visible as a child of node 1 446 | await expect(tree.getByTestId("5")).toBeVisible(); // New child 447 | }); 448 | 449 | test("exposes appendNode method with hierarchy data format #2", async ({ 450 | initTestBed, 451 | createTreeDriver, 452 | createButtonDriver, 453 | }) => { 454 | await initTestBed(` 455 | <Fragment> 456 | <VStack height="400px"> 457 | <Tree id="treeApi" testId="tree" 458 | dataFormat="hierarchy" 459 | defaultExpanded="all" 460 | data='{${JSON.stringify(hierarchyTreeData)}}'> 461 | <property name="itemTemplate"> 462 | <HStack testId="{$item.id}" verticalAlignment="center"> 463 | <Text value="{$item.name}" /> 464 | </HStack> 465 | </property> 466 | </Tree> 467 | </VStack> 468 | <Button id="appendBtn" testId="append-btn" label="Append new root node" 469 | onClick="treeApi.appendNode(null, { id: 5, name: 'New Root Item' });" /> 470 | </Fragment> 471 | `); 472 | 473 | const tree = await createTreeDriver("tree"); 474 | const appendButton = await createButtonDriver("append-btn"); 475 | 476 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 477 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 478 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 479 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 480 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 481 | 482 | // Append new root node using API 483 | await appendButton.click(); 484 | 485 | // Verify original nodes are still visible 486 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 487 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 488 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 489 | await expect(tree.getByTestId("4")).toBeVisible(); // Original grandchild 490 | 491 | // Verify new root node is now visible 492 | await expect(tree.getByTestId("5")).toBeVisible(); // New root node 493 | }); 494 | 495 | test("exposes removeNode method with flat data format #1 - remove leaf node", async ({ 496 | initTestBed, 497 | createTreeDriver, 498 | createButtonDriver, 499 | }) => { 500 | await initTestBed(` 501 | <Fragment> 502 | <VStack height="400px"> 503 | <Tree id="treeApi" testId="tree" 504 | dataFormat="flat" 505 | defaultExpanded="all" 506 | data='{${JSON.stringify(flatTreeData)}}'> 507 | <property name="itemTemplate"> 508 | <HStack testId="{$item.id}" verticalAlignment="center"> 509 | <Text value="{$item.name}" /> 510 | </HStack> 511 | </property> 512 | </Tree> 513 | </VStack> 514 | <Button id="removeBtn" testId="remove-btn" label="Remove Leaf Node 4" 515 | onClick="treeApi.removeNode(4);" /> 516 | </Fragment> 517 | `); 518 | 519 | const tree = await createTreeDriver("tree"); 520 | const removeButton = await createButtonDriver("remove-btn"); 521 | 522 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 523 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 524 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 525 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 526 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 527 | 528 | // Remove leaf node using API 529 | await removeButton.click(); 530 | 531 | // Verify original nodes are still visible except removed node 532 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 533 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 534 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 535 | 536 | // Verify removed node is no longer visible 537 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed leaf node 538 | }); 539 | 540 | test("exposes removeNode method with flat data format #2 - remove parent with children", async ({ 541 | initTestBed, 542 | createTreeDriver, 543 | createButtonDriver, 544 | }) => { 545 | await initTestBed(` 546 | <Fragment> 547 | <VStack height="400px"> 548 | <Tree id="treeApi" testId="tree" 549 | dataFormat="flat" 550 | defaultExpanded="all" 551 | data='{${JSON.stringify(flatTreeData)}}'> 552 | <property name="itemTemplate"> 553 | <HStack testId="{$item.id}" verticalAlignment="center"> 554 | <Text value="{$item.name}" /> 555 | </HStack> 556 | </property> 557 | </Tree> 558 | </VStack> 559 | <Button id="removeBtn" testId="remove-btn" label="Remove Node 2 and its children" 560 | onClick="treeApi.removeNode(2);" /> 561 | </Fragment> 562 | `); 563 | 564 | const tree = await createTreeDriver("tree"); 565 | const removeButton = await createButtonDriver("remove-btn"); 566 | 567 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 568 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 569 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 570 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 571 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 572 | 573 | // Remove parent node and its children using API 574 | await removeButton.click(); 575 | 576 | // Verify remaining nodes are still visible 577 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 578 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child (not removed) 579 | 580 | // Verify removed nodes are no longer visible 581 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Removed parent 582 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed child (descendant of node 2) 583 | }); 584 | 585 | test("exposes removeNode method with flat data format #3 - remove root node", async ({ 586 | initTestBed, 587 | createTreeDriver, 588 | createButtonDriver, 589 | }) => { 590 | await initTestBed(` 591 | <Fragment> 592 | <VStack height="400px"> 593 | <Tree id="treeApi" testId="tree" 594 | dataFormat="flat" 595 | defaultExpanded="all" 596 | data='{${JSON.stringify(flatTreeData)}}'> 597 | <property name="itemTemplate"> 598 | <HStack testId="{$item.id}" verticalAlignment="center"> 599 | <Text value="{$item.name}" /> 600 | </HStack> 601 | </property> 602 | </Tree> 603 | </VStack> 604 | <Button id="removeBtn" testId="remove-btn" label="Remove Root Node 1 and all descendants" 605 | onClick="treeApi.removeNode(1);" /> 606 | </Fragment> 607 | `); 608 | 609 | const tree = await createTreeDriver("tree"); 610 | const removeButton = await createButtonDriver("remove-btn"); 611 | 612 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 613 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 614 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 615 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 616 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 617 | 618 | // Remove root node and all descendants using API 619 | await removeButton.click(); 620 | 621 | // Verify all nodes are removed since they were all descendants of node 1 622 | await expect(tree.getByTestId("1")).not.toBeVisible(); // Removed root 623 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Removed descendant 624 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Removed descendant 625 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed descendant 626 | }); 627 | 628 | test("exposes removeChildren method with flat data format #1 - remove children of parent node", async ({ 629 | initTestBed, 630 | createTreeDriver, 631 | createButtonDriver, 632 | }) => { 633 | await initTestBed(` 634 | <Fragment> 635 | <VStack height="400px"> 636 | <Tree id="treeApi" testId="tree" 637 | dataFormat="flat" 638 | defaultExpanded="all" 639 | data='{${JSON.stringify(flatTreeData)}}'> 640 | <property name="itemTemplate"> 641 | <HStack testId="{$item.id}" verticalAlignment="center"> 642 | <Text value="{$item.name}" /> 643 | </HStack> 644 | </property> 645 | </Tree> 646 | </VStack> 647 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Node 1" 648 | onClick="treeApi.removeChildren(1);" /> 649 | </Fragment> 650 | `); 651 | 652 | const tree = await createTreeDriver("tree"); 653 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 654 | 655 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 656 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 657 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 658 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 659 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 660 | 661 | // Remove children of node 1 using API 662 | await removeChildrenButton.click(); 663 | 664 | // Verify parent node 1 is still visible 665 | await expect(tree.getByTestId("1")).toBeVisible(); // Parent node kept 666 | 667 | // Verify all children and descendants are removed 668 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Removed child 669 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Removed child 670 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed descendant 671 | }); 672 | 673 | test("exposes removeChildren method with flat data format #2 - remove children of node with one child", async ({ 674 | initTestBed, 675 | createTreeDriver, 676 | createButtonDriver, 677 | }) => { 678 | await initTestBed(` 679 | <Fragment> 680 | <VStack height="400px"> 681 | <Tree id="treeApi" testId="tree" 682 | dataFormat="flat" 683 | defaultExpanded="all" 684 | data='{${JSON.stringify(flatTreeData)}}'> 685 | <property name="itemTemplate"> 686 | <HStack testId="{$item.id}" verticalAlignment="center"> 687 | <Text value="{$item.name}" /> 688 | </HStack> 689 | </property> 690 | </Tree> 691 | </VStack> 692 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Node 2" 693 | onClick="treeApi.removeChildren(2);" /> 694 | </Fragment> 695 | `); 696 | 697 | const tree = await createTreeDriver("tree"); 698 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 699 | 700 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 701 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 702 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 703 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 704 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 705 | 706 | // Remove children of node 2 using API 707 | await removeChildrenButton.click(); 708 | 709 | // Verify nodes 1, 2, and 3 are still visible (not affected) 710 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 711 | await expect(tree.getByTestId("2")).toBeVisible(); // Parent node kept 712 | await expect(tree.getByTestId("3")).toBeVisible(); // Sibling not affected 713 | 714 | // Verify only node 4 (child of node 2) is removed 715 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed child 716 | }); 717 | 718 | test("exposes removeChildren method with flat data format #3 - remove children of leaf node (no effect)", async ({ 719 | initTestBed, 720 | createTreeDriver, 721 | createButtonDriver, 722 | }) => { 723 | await initTestBed(` 724 | <Fragment> 725 | <VStack height="400px"> 726 | <Tree id="treeApi" testId="tree" 727 | dataFormat="flat" 728 | defaultExpanded="all" 729 | data='{${JSON.stringify(flatTreeData)}}'> 730 | <property name="itemTemplate"> 731 | <HStack testId="{$item.id}" verticalAlignment="center"> 732 | <Text value="{$item.name}" /> 733 | </HStack> 734 | </property> 735 | </Tree> 736 | </VStack> 737 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Leaf Node 4" 738 | onClick="treeApi.removeChildren(4);" /> 739 | </Fragment> 740 | `); 741 | 742 | const tree = await createTreeDriver("tree"); 743 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 744 | 745 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 746 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 747 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 748 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 749 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 750 | 751 | // Remove children of leaf node 4 using API (should have no effect) 752 | await removeChildrenButton.click(); 753 | 754 | // Verify all nodes are still visible (no changes since node 4 has no children) 755 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 756 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 757 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 758 | await expect(tree.getByTestId("4")).toBeVisible(); // Leaf node still visible 759 | }); 760 | 761 | test("exposes removeNode method with hierarchy data format #1 - remove leaf node", async ({ 762 | initTestBed, 763 | createTreeDriver, 764 | createButtonDriver, 765 | }) => { 766 | await initTestBed(` 767 | <Fragment> 768 | <VStack height="400px"> 769 | <Tree id="treeApi" testId="tree" 770 | dataFormat="hierarchy" 771 | defaultExpanded="all" 772 | data='{${JSON.stringify(hierarchyTreeData)}}'> 773 | <property name="itemTemplate"> 774 | <HStack testId="{$item.id}" verticalAlignment="center"> 775 | <Text value="{$item.name}" /> 776 | </HStack> 777 | </property> 778 | </Tree> 779 | </VStack> 780 | <Button id="removeBtn" testId="remove-btn" label="Remove Leaf Node 4" 781 | onClick="treeApi.removeNode(4);" /> 782 | </Fragment> 783 | `); 784 | 785 | const tree = await createTreeDriver("tree"); 786 | const removeButton = await createButtonDriver("remove-btn"); 787 | 788 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 789 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 790 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 791 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 792 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 793 | 794 | // Remove leaf node using API 795 | await removeButton.click(); 796 | 797 | // Verify original nodes are still visible except removed node 798 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 799 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child 800 | await expect(tree.getByTestId("3")).toBeVisible(); // Original child 801 | 802 | // Verify removed node is no longer visible 803 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed leaf node 804 | }); 805 | 806 | test("exposes removeNode method with hierarchy data format #2 - remove parent with children", async ({ 807 | initTestBed, 808 | createTreeDriver, 809 | createButtonDriver, 810 | }) => { 811 | await initTestBed(` 812 | <Fragment> 813 | <VStack height="400px"> 814 | <Tree id="treeApi" testId="tree" 815 | dataFormat="hierarchy" 816 | defaultExpanded="all" 817 | data='{${JSON.stringify(hierarchyTreeData)}}'> 818 | <property name="itemTemplate"> 819 | <HStack testId="{$item.id}" verticalAlignment="center"> 820 | <Text value="{$item.name}" /> 821 | </HStack> 822 | </property> 823 | </Tree> 824 | </VStack> 825 | <Button id="removeBtn" testId="remove-btn" label="Remove Node 3 and its children" 826 | onClick="treeApi.removeNode(3);" /> 827 | </Fragment> 828 | `); 829 | 830 | const tree = await createTreeDriver("tree"); 831 | const removeButton = await createButtonDriver("remove-btn"); 832 | 833 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 834 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 835 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 836 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 837 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 838 | 839 | // Remove parent node and its children using API 840 | await removeButton.click(); 841 | 842 | // Verify remaining nodes are still visible 843 | await expect(tree.getByTestId("1")).toBeVisible(); // Original root 844 | await expect(tree.getByTestId("2")).toBeVisible(); // Original child (not removed) 845 | 846 | // Verify removed nodes are no longer visible 847 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Removed parent 848 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed child (descendant of node 3) 849 | }); 850 | 851 | test("exposes removeNode method with hierarchy data format #3 - remove root node", async ({ 852 | initTestBed, 853 | createTreeDriver, 854 | createButtonDriver, 855 | }) => { 856 | await initTestBed(` 857 | <Fragment> 858 | <VStack height="400px"> 859 | <Tree id="treeApi" testId="tree" 860 | dataFormat="hierarchy" 861 | defaultExpanded="all" 862 | data='{${JSON.stringify(hierarchyTreeData)}}'> 863 | <property name="itemTemplate"> 864 | <HStack testId="{$item.id}" verticalAlignment="center"> 865 | <Text value="{$item.name}" /> 866 | </HStack> 867 | </property> 868 | </Tree> 869 | </VStack> 870 | <Button id="removeBtn" testId="remove-btn" label="Remove Root Node 1 and all descendants" 871 | onClick="treeApi.removeNode(1);" /> 872 | </Fragment> 873 | `); 874 | 875 | const tree = await createTreeDriver("tree"); 876 | const removeButton = await createButtonDriver("remove-btn"); 877 | 878 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 879 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 880 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 881 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 882 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 883 | 884 | // Remove root node and all descendants using API 885 | await removeButton.click(); 886 | 887 | // Verify all nodes are removed since they were all descendants of node 1 888 | await expect(tree.getByTestId("1")).not.toBeVisible(); // Removed root 889 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Removed descendant 890 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Removed descendant 891 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed descendant 892 | }); 893 | 894 | test("exposes removeChildren method with hierarchy data format #1 - remove children of parent node", async ({ 895 | initTestBed, 896 | createTreeDriver, 897 | createButtonDriver, 898 | }) => { 899 | await initTestBed(` 900 | <Fragment> 901 | <VStack height="400px"> 902 | <Tree id="treeApi" testId="tree" 903 | dataFormat="hierarchy" 904 | defaultExpanded="all" 905 | data='{${JSON.stringify(hierarchyTreeData)}}'> 906 | <property name="itemTemplate"> 907 | <HStack testId="{$item.id}" verticalAlignment="center"> 908 | <Text value="{$item.name}" /> 909 | </HStack> 910 | </property> 911 | </Tree> 912 | </VStack> 913 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Node 1" 914 | onClick="treeApi.removeChildren(1);" /> 915 | </Fragment> 916 | `); 917 | 918 | const tree = await createTreeDriver("tree"); 919 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 920 | 921 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 922 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 923 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 924 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 925 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 926 | 927 | // Remove children of node 1 using API 928 | await removeChildrenButton.click(); 929 | 930 | // Verify parent node 1 is still visible 931 | await expect(tree.getByTestId("1")).toBeVisible(); // Parent node kept 932 | 933 | // Verify all children and descendants are removed 934 | await expect(tree.getByTestId("2")).not.toBeVisible(); // Removed child 935 | await expect(tree.getByTestId("3")).not.toBeVisible(); // Removed child 936 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed descendant 937 | }); 938 | 939 | test("exposes removeChildren method with hierarchy data format #2 - remove children of node with children", async ({ 940 | initTestBed, 941 | createTreeDriver, 942 | createButtonDriver, 943 | }) => { 944 | await initTestBed(` 945 | <Fragment> 946 | <VStack height="400px"> 947 | <Tree id="treeApi" testId="tree" 948 | dataFormat="hierarchy" 949 | defaultExpanded="all" 950 | data='{${JSON.stringify(hierarchyTreeData)}}'> 951 | <property name="itemTemplate"> 952 | <HStack testId="{$item.id}" verticalAlignment="center"> 953 | <Text value="{$item.name}" /> 954 | </HStack> 955 | </property> 956 | </Tree> 957 | </VStack> 958 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Node 3" 959 | onClick="treeApi.removeChildren(3);" /> 960 | </Fragment> 961 | `); 962 | 963 | const tree = await createTreeDriver("tree"); 964 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 965 | 966 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 967 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 968 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 969 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 970 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 971 | 972 | // Remove children of node 3 using API 973 | await removeChildrenButton.click(); 974 | 975 | // Verify nodes 1, 2, and 3 are still visible (not affected) 976 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 977 | await expect(tree.getByTestId("2")).toBeVisible(); // Sibling not affected 978 | await expect(tree.getByTestId("3")).toBeVisible(); // Parent node kept 979 | 980 | // Verify only node 4 (child of node 3) is removed 981 | await expect(tree.getByTestId("4")).not.toBeVisible(); // Removed child 982 | }); 983 | 984 | test("exposes removeChildren method with hierarchy data format #3 - remove children of leaf node (no effect)", async ({ 985 | initTestBed, 986 | createTreeDriver, 987 | createButtonDriver, 988 | }) => { 989 | await initTestBed(` 990 | <Fragment> 991 | <VStack height="400px"> 992 | <Tree id="treeApi" testId="tree" 993 | dataFormat="hierarchy" 994 | defaultExpanded="all" 995 | data='{${JSON.stringify(hierarchyTreeData)}}'> 996 | <property name="itemTemplate"> 997 | <HStack testId="{$item.id}" verticalAlignment="center"> 998 | <Text value="{$item.name}" /> 999 | </HStack> 1000 | </property> 1001 | </Tree> 1002 | </VStack> 1003 | <Button id="removeChildrenBtn" testId="remove-children-btn" label="Remove children of Leaf Node 2" 1004 | onClick="treeApi.removeChildren(2);" /> 1005 | </Fragment> 1006 | `); 1007 | 1008 | const tree = await createTreeDriver("tree"); 1009 | const removeChildrenButton = await createButtonDriver("remove-children-btn"); 1010 | 1011 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1012 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1013 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1014 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1015 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1016 | 1017 | // Remove children of leaf node 2 using API (should have no effect) 1018 | await removeChildrenButton.click(); 1019 | 1020 | // Verify all nodes are still visible (no changes since node 2 has no children in hierarchy data) 1021 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1022 | await expect(tree.getByTestId("2")).toBeVisible(); // Leaf node still visible 1023 | await expect(tree.getByTestId("3")).toBeVisible(); // Sibling still visible 1024 | await expect(tree.getByTestId("4")).toBeVisible(); // Child of sibling still visible 1025 | }); 1026 | 1027 | test("exposes insertNodeBefore method with flat data format #1 - insert before sibling node", async ({ 1028 | initTestBed, 1029 | createTreeDriver, 1030 | createButtonDriver, 1031 | }) => { 1032 | await initTestBed(` 1033 | <Fragment> 1034 | <VStack height="400px"> 1035 | <Tree id="treeApi" testId="tree" 1036 | dataFormat="flat" 1037 | defaultExpanded="all" 1038 | data='{${JSON.stringify(flatTreeData)}}'> 1039 | <property name="itemTemplate"> 1040 | <HStack testId="{$item.id}" verticalAlignment="center"> 1041 | <Text value="{$item.name}" /> 1042 | </HStack> 1043 | </property> 1044 | </Tree> 1045 | </VStack> 1046 | <Button id="insertBtn" testId="insert-btn" label="Insert before Node 3" 1047 | onClick="treeApi.insertNodeBefore(3, { id: 5, name: 'New Node Before 3' });" /> 1048 | </Fragment> 1049 | `); 1050 | 1051 | const tree = await createTreeDriver("tree"); 1052 | const insertButton = await createButtonDriver("insert-btn"); 1053 | 1054 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1055 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1056 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1057 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1058 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1059 | 1060 | // Insert new node before node 3 using API 1061 | await insertButton.click(); 1062 | 1063 | // Verify all original nodes are still visible 1064 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1065 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1066 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1067 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1068 | 1069 | // Verify new node is visible (inserted as sibling of node 3) 1070 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1071 | }); 1072 | 1073 | test("exposes insertNodeBefore method with flat data format #2 - insert before first child", async ({ 1074 | initTestBed, 1075 | createTreeDriver, 1076 | createButtonDriver, 1077 | }) => { 1078 | await initTestBed(` 1079 | <Fragment> 1080 | <VStack height="400px"> 1081 | <Tree id="treeApi" testId="tree" 1082 | dataFormat="flat" 1083 | defaultExpanded="all" 1084 | data='{${JSON.stringify(flatTreeData)}}'> 1085 | <property name="itemTemplate"> 1086 | <HStack testId="{$item.id}" verticalAlignment="center"> 1087 | <Text value="{$item.name}" /> 1088 | </HStack> 1089 | </property> 1090 | </Tree> 1091 | </VStack> 1092 | <Button id="insertBtn" testId="insert-btn" label="Insert before Node 2" 1093 | onClick="treeApi.insertNodeBefore(2, { id: 5, name: 'New First Child' });" /> 1094 | </Fragment> 1095 | `); 1096 | 1097 | const tree = await createTreeDriver("tree"); 1098 | const insertButton = await createButtonDriver("insert-btn"); 1099 | 1100 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1101 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1102 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1103 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1104 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1105 | 1106 | // Insert new node before node 2 using API 1107 | await insertButton.click(); 1108 | 1109 | // Verify all original nodes are still visible 1110 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1111 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1112 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1113 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1114 | 1115 | // Verify new node is visible (inserted as first child of node 1) 1116 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1117 | }); 1118 | 1119 | test("exposes insertNodeBefore method with flat data format #3 - insert before root node", async ({ 1120 | initTestBed, 1121 | createTreeDriver, 1122 | createButtonDriver, 1123 | }) => { 1124 | await initTestBed(` 1125 | <Fragment> 1126 | <VStack height="400px"> 1127 | <Tree id="treeApi" testId="tree" 1128 | dataFormat="flat" 1129 | defaultExpanded="all" 1130 | data='{${JSON.stringify(flatTreeData)}}'> 1131 | <property name="itemTemplate"> 1132 | <HStack testId="{$item.id}" verticalAlignment="center"> 1133 | <Text value="{$item.name}" /> 1134 | </HStack> 1135 | </property> 1136 | </Tree> 1137 | </VStack> 1138 | <Button id="insertBtn" testId="insert-btn" label="Insert before Root Node 1" 1139 | onClick="treeApi.insertNodeBefore(1, { id: 5, name: 'New Root Before 1' });" /> 1140 | </Fragment> 1141 | `); 1142 | 1143 | const tree = await createTreeDriver("tree"); 1144 | const insertButton = await createButtonDriver("insert-btn"); 1145 | 1146 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1147 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1148 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1149 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1150 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1151 | 1152 | // Insert new node before root node 1 using API 1153 | await insertButton.click(); 1154 | 1155 | // Verify all original nodes are still visible 1156 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1157 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1158 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1159 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1160 | 1161 | // Verify new node is visible (inserted as new root before node 1) 1162 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1163 | }); 1164 | 1165 | test("exposes insertNodeBefore method with hierarchy data format #1 - insert before sibling node", async ({ 1166 | initTestBed, 1167 | createTreeDriver, 1168 | createButtonDriver, 1169 | }) => { 1170 | await initTestBed(` 1171 | <Fragment> 1172 | <VStack height="400px"> 1173 | <Tree id="treeApi" testId="tree" 1174 | dataFormat="hierarchy" 1175 | defaultExpanded="all" 1176 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1177 | <property name="itemTemplate"> 1178 | <HStack testId="{$item.id}" verticalAlignment="center"> 1179 | <Text value="{$item.name}" /> 1180 | </HStack> 1181 | </property> 1182 | </Tree> 1183 | </VStack> 1184 | <Button id="insertBtn" testId="insert-btn" label="Insert before Node 3" 1185 | onClick="treeApi.insertNodeBefore(3, { id: 5, name: 'New Node Before 3' });" /> 1186 | </Fragment> 1187 | `); 1188 | 1189 | const tree = await createTreeDriver("tree"); 1190 | const insertButton = await createButtonDriver("insert-btn"); 1191 | 1192 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1193 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1194 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1195 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1196 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1197 | 1198 | // Insert new node before node 3 using API 1199 | await insertButton.click(); 1200 | 1201 | // Verify all original nodes are still visible 1202 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1203 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1204 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1205 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1206 | 1207 | // Verify new node is visible (inserted as sibling of node 3) 1208 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1209 | }); 1210 | 1211 | test("exposes insertNodeBefore method with hierarchy data format #2 - insert before first child", async ({ 1212 | initTestBed, 1213 | createTreeDriver, 1214 | createButtonDriver, 1215 | }) => { 1216 | await initTestBed(` 1217 | <Fragment> 1218 | <VStack height="400px"> 1219 | <Tree id="treeApi" testId="tree" 1220 | dataFormat="hierarchy" 1221 | defaultExpanded="all" 1222 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1223 | <property name="itemTemplate"> 1224 | <HStack testId="{$item.id}" verticalAlignment="center"> 1225 | <Text value="{$item.name}" /> 1226 | </HStack> 1227 | </property> 1228 | </Tree> 1229 | </VStack> 1230 | <Button id="insertBtn" testId="insert-btn" label="Insert before Node 2" 1231 | onClick="treeApi.insertNodeBefore(2, { id: 5, name: 'New First Child' });" /> 1232 | </Fragment> 1233 | `); 1234 | 1235 | const tree = await createTreeDriver("tree"); 1236 | const insertButton = await createButtonDriver("insert-btn"); 1237 | 1238 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1239 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1240 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1241 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1242 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1243 | 1244 | // Insert new node before node 2 using API 1245 | await insertButton.click(); 1246 | 1247 | // Verify all original nodes are still visible 1248 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1249 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1250 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1251 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1252 | 1253 | // Verify new node is visible (inserted as first child of node 1) 1254 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1255 | }); 1256 | 1257 | test("exposes insertNodeBefore method with hierarchy data format #3 - insert before root node", async ({ 1258 | initTestBed, 1259 | createTreeDriver, 1260 | createButtonDriver, 1261 | }) => { 1262 | await initTestBed(` 1263 | <Fragment> 1264 | <VStack height="400px"> 1265 | <Tree id="treeApi" testId="tree" 1266 | dataFormat="hierarchy" 1267 | defaultExpanded="all" 1268 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1269 | <property name="itemTemplate"> 1270 | <HStack testId="{$item.id}" verticalAlignment="center"> 1271 | <Text value="{$item.name}" /> 1272 | </HStack> 1273 | </property> 1274 | </Tree> 1275 | </VStack> 1276 | <Button id="insertBtn" testId="insert-btn" label="Insert before Root Node 1" 1277 | onClick="treeApi.insertNodeBefore(1, { id: 5, name: 'New Root Before 1' });" /> 1278 | </Fragment> 1279 | `); 1280 | 1281 | const tree = await createTreeDriver("tree"); 1282 | const insertButton = await createButtonDriver("insert-btn"); 1283 | 1284 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1285 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1286 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1287 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1288 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1289 | 1290 | // Insert new node before root node 1 using API 1291 | await insertButton.click(); 1292 | 1293 | // Verify all original nodes are still visible 1294 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1295 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1296 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1297 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1298 | 1299 | // Verify new node is visible (inserted as new root before node 1) 1300 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1301 | }); 1302 | 1303 | test("exposes insertNodeAfter method with flat data format #1 - insert after sibling node", async ({ 1304 | initTestBed, 1305 | createTreeDriver, 1306 | createButtonDriver, 1307 | }) => { 1308 | await initTestBed(` 1309 | <Fragment> 1310 | <VStack height="400px"> 1311 | <Tree id="treeApi" testId="tree" 1312 | dataFormat="flat" 1313 | defaultExpanded="all" 1314 | data='{${JSON.stringify(flatTreeData)}}'> 1315 | <property name="itemTemplate"> 1316 | <HStack testId="{$item.id}" verticalAlignment="center"> 1317 | <Text value="{$item.name}" /> 1318 | </HStack> 1319 | </property> 1320 | </Tree> 1321 | </VStack> 1322 | <Button id="insertBtn" testId="insert-btn" label="Insert after Node 2" 1323 | onClick="treeApi.insertNodeAfter(2, { id: 5, name: 'New Node After 2' });" /> 1324 | </Fragment> 1325 | `); 1326 | 1327 | const tree = await createTreeDriver("tree"); 1328 | const insertButton = await createButtonDriver("insert-btn"); 1329 | 1330 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1331 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1332 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1333 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1334 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1335 | 1336 | // Insert new node after node 2 using API 1337 | await insertButton.click(); 1338 | 1339 | // Verify all original nodes are still visible 1340 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1341 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1342 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1343 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1344 | 1345 | // Verify new node is visible (inserted as sibling after node 2) 1346 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1347 | }); 1348 | 1349 | test("exposes insertNodeAfter method with flat data format #2 - insert after last child", async ({ 1350 | initTestBed, 1351 | createTreeDriver, 1352 | createButtonDriver, 1353 | }) => { 1354 | await initTestBed(` 1355 | <Fragment> 1356 | <VStack height="400px"> 1357 | <Tree id="treeApi" testId="tree" 1358 | dataFormat="flat" 1359 | defaultExpanded="all" 1360 | data='{${JSON.stringify(flatTreeData)}}'> 1361 | <property name="itemTemplate"> 1362 | <HStack testId="{$item.id}" verticalAlignment="center"> 1363 | <Text value="{$item.name}" /> 1364 | </HStack> 1365 | </property> 1366 | </Tree> 1367 | </VStack> 1368 | <Button id="insertBtn" testId="insert-btn" label="Insert after Node 3" 1369 | onClick="treeApi.insertNodeAfter(3, { id: 5, name: 'New Last Child' });" /> 1370 | </Fragment> 1371 | `); 1372 | 1373 | const tree = await createTreeDriver("tree"); 1374 | const insertButton = await createButtonDriver("insert-btn"); 1375 | 1376 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1377 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1378 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1379 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1380 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1381 | 1382 | // Insert new node after node 3 using API 1383 | await insertButton.click(); 1384 | 1385 | // Verify all original nodes are still visible 1386 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1387 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1388 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1389 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1390 | 1391 | // Verify new node is visible (inserted as last child of node 1) 1392 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1393 | }); 1394 | 1395 | test("exposes insertNodeAfter method with flat data format #3 - insert after root node", async ({ 1396 | initTestBed, 1397 | createTreeDriver, 1398 | createButtonDriver, 1399 | }) => { 1400 | await initTestBed(` 1401 | <Fragment> 1402 | <VStack height="400px"> 1403 | <Tree id="treeApi" testId="tree" 1404 | dataFormat="flat" 1405 | defaultExpanded="all" 1406 | data='{${JSON.stringify(flatTreeData)}}'> 1407 | <property name="itemTemplate"> 1408 | <HStack testId="{$item.id}" verticalAlignment="center"> 1409 | <Text value="{$item.name}" /> 1410 | </HStack> 1411 | </property> 1412 | </Tree> 1413 | </VStack> 1414 | <Button id="insertBtn" testId="insert-btn" label="Insert after Root Node 1" 1415 | onClick="treeApi.insertNodeAfter(1, { id: 5, name: 'New Root After 1' });" /> 1416 | </Fragment> 1417 | `); 1418 | 1419 | const tree = await createTreeDriver("tree"); 1420 | const insertButton = await createButtonDriver("insert-btn"); 1421 | 1422 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1423 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1424 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1425 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1426 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1427 | 1428 | // Insert new node after root node 1 using API 1429 | await insertButton.click(); 1430 | 1431 | // Verify all original nodes are still visible 1432 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1433 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1434 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1435 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1436 | 1437 | // Verify new node is visible (inserted as new root after node 1) 1438 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1439 | }); 1440 | 1441 | test("exposes insertNodeAfter method with hierarchy data format #1 - insert after sibling node", async ({ 1442 | initTestBed, 1443 | createTreeDriver, 1444 | createButtonDriver, 1445 | }) => { 1446 | await initTestBed(` 1447 | <Fragment> 1448 | <VStack height="400px"> 1449 | <Tree id="treeApi" testId="tree" 1450 | dataFormat="hierarchy" 1451 | defaultExpanded="all" 1452 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1453 | <property name="itemTemplate"> 1454 | <HStack testId="{$item.id}" verticalAlignment="center"> 1455 | <Text value="{$item.name}" /> 1456 | </HStack> 1457 | </property> 1458 | </Tree> 1459 | </VStack> 1460 | <Button id="insertBtn" testId="insert-btn" label="Insert after Node 2" 1461 | onClick="treeApi.insertNodeAfter(2, { id: 5, name: 'New Node After 2' });" /> 1462 | </Fragment> 1463 | `); 1464 | 1465 | const tree = await createTreeDriver("tree"); 1466 | const insertButton = await createButtonDriver("insert-btn"); 1467 | 1468 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1469 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1470 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1471 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1472 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1473 | 1474 | // Insert new node after node 2 using API 1475 | await insertButton.click(); 1476 | 1477 | // Verify all original nodes are still visible 1478 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1479 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1480 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1481 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1482 | 1483 | // Verify new node is visible (inserted as sibling after node 2) 1484 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1485 | }); 1486 | 1487 | test("exposes insertNodeAfter method with hierarchy data format #2 - insert after last child", async ({ 1488 | initTestBed, 1489 | createTreeDriver, 1490 | createButtonDriver, 1491 | }) => { 1492 | await initTestBed(` 1493 | <Fragment> 1494 | <VStack height="400px"> 1495 | <Tree id="treeApi" testId="tree" 1496 | dataFormat="hierarchy" 1497 | defaultExpanded="all" 1498 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1499 | <property name="itemTemplate"> 1500 | <HStack testId="{$item.id}" verticalAlignment="center"> 1501 | <Text value="{$item.name}" /> 1502 | </HStack> 1503 | </property> 1504 | </Tree> 1505 | </VStack> 1506 | <Button id="insertBtn" testId="insert-btn" label="Insert after Node 3" 1507 | onClick="treeApi.insertNodeAfter(3, { id: 5, name: 'New Last Child' });" /> 1508 | </Fragment> 1509 | `); 1510 | 1511 | const tree = await createTreeDriver("tree"); 1512 | const insertButton = await createButtonDriver("insert-btn"); 1513 | 1514 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1515 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1516 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1517 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1518 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1519 | 1520 | // Insert new node after node 3 using API 1521 | await insertButton.click(); 1522 | 1523 | // Verify all original nodes are still visible 1524 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1525 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1526 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1527 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1528 | 1529 | // Verify new node is visible (inserted as last child of node 1) 1530 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1531 | }); 1532 | 1533 | test("exposes insertNodeAfter method with hierarchy data format #3 - insert after root node", async ({ 1534 | initTestBed, 1535 | createTreeDriver, 1536 | createButtonDriver, 1537 | }) => { 1538 | await initTestBed(` 1539 | <Fragment> 1540 | <VStack height="400px"> 1541 | <Tree id="treeApi" testId="tree" 1542 | dataFormat="hierarchy" 1543 | defaultExpanded="all" 1544 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1545 | <property name="itemTemplate"> 1546 | <HStack testId="{$item.id}" verticalAlignment="center"> 1547 | <Text value="{$item.name}" /> 1548 | </HStack> 1549 | </property> 1550 | </Tree> 1551 | </VStack> 1552 | <Button id="insertBtn" testId="insert-btn" label="Insert after Root Node 1" 1553 | onClick="treeApi.insertNodeAfter(1, { id: 5, name: 'New Root After 1' });" /> 1554 | </Fragment> 1555 | `); 1556 | 1557 | const tree = await createTreeDriver("tree"); 1558 | const insertButton = await createButtonDriver("insert-btn"); 1559 | 1560 | // Check initial tree structure - all nodes visible due to defaultExpanded="all" 1561 | await expect(tree.getByTestId("1")).toBeVisible(); // Root visible 1562 | await expect(tree.getByTestId("2")).toBeVisible(); // Child visible (expanded) 1563 | await expect(tree.getByTestId("3")).toBeVisible(); // Child visible (expanded) 1564 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild visible (expanded) 1565 | 1566 | // Insert new node after root node 1 using API 1567 | await insertButton.click(); 1568 | 1569 | // Verify all original nodes are still visible 1570 | await expect(tree.getByTestId("1")).toBeVisible(); // Root still visible 1571 | await expect(tree.getByTestId("2")).toBeVisible(); // Child still visible 1572 | await expect(tree.getByTestId("3")).toBeVisible(); // Child still visible 1573 | await expect(tree.getByTestId("4")).toBeVisible(); // Grandchild still visible 1574 | 1575 | // Verify new node is visible (inserted as new root after node 1) 1576 | await expect(tree.getByTestId("5")).toBeVisible(); // New node inserted 1577 | }); 1578 | 1579 | test("exposes scrollIntoView method", async ({ 1580 | initTestBed, 1581 | createTreeDriver, 1582 | createButtonDriver, 1583 | }) => { 1584 | // Create a deeper hierarchy to test scrollIntoView with expansion 1585 | const deepTreeData = [ 1586 | { id: 1, name: "Root Item 1", parentId: null }, 1587 | { id: 2, name: "Child Item 1.1", parentId: 1 }, 1588 | { id: 3, name: "Child Item 1.2", parentId: 1 }, 1589 | { id: 4, name: "Grandchild Item 1.1.1", parentId: 2 }, 1590 | { id: 5, name: "Great-grandchild Item 1.1.1.1", parentId: 4 }, 1591 | { id: 6, name: "Root Item 2", parentId: null }, 1592 | { id: 7, name: "Child Item 2.1", parentId: 6 }, 1593 | { id: 8, name: "Child Item 2.2", parentId: 6 }, 1594 | { id: 9, name: "Grandchild Item 2.1.1", parentId: 7 }, 1595 | { id: 10, name: "Root Item 3", parentId: null }, 1596 | { id: 11, name: "Child Item 3.1", parentId: 10 }, 1597 | { id: 12, name: "Deeply Nested Target", parentId: 11 }, // This requires expansion to be visible 1598 | ]; 1599 | 1600 | const { testStateDriver } = await initTestBed(` 1601 | <Fragment> 1602 | <VStack height="100px"> 1603 | <Tree id="treeApi" testId="tree" 1604 | dataFormat="flat" 1605 | parentIdField="parentId" 1606 | data='{${JSON.stringify(deepTreeData)}}'> 1607 | <property name="itemTemplate"> 1608 | <HStack testId="{$item.id}:scrollview" verticalAlignment="center"> 1609 | <Text value="{$item.name}" /> 1610 | </HStack> 1611 | </property> 1612 | </Tree> 1613 | </VStack> 1614 | <Button id="scrollViewBtn" testId="scroll-view-btn" label="Scroll Into View Deep Target" onClick=" 1615 | treeApi.scrollIntoView('12'); 1616 | testState = { actionPerformed: 'scrollIntoView', itemId: '12' }; 1617 | " /> 1618 | </Fragment> 1619 | `); 1620 | 1621 | const tree = await createTreeDriver("tree"); 1622 | const scrollViewButton = await createButtonDriver("scroll-view-btn"); 1623 | 1624 | // Initially, tree should be collapsed so the deep target is not visible 1625 | await expect(tree.getByTestId("1:scrollview")).toBeVisible(); // Root visible 1626 | await expect(tree.getByTestId("6:scrollview")).toBeVisible(); // Root visible 1627 | await expect(tree.getByTestId("10:scrollview")).toBeVisible(); // Root visible 1628 | await expect(tree.getByTestId("11:scrollview")).not.toBeVisible(); // Child hidden (collapsed) 1629 | await expect(tree.getByTestId("12:scrollview")).not.toBeVisible(); // Deep target hidden (needs expansion) 1630 | 1631 | // Click scroll into view button 1632 | await scrollViewButton.click(); 1633 | 1634 | // Verify test state confirms action was performed 1635 | await expect.poll(testStateDriver.testState).toEqual({ 1636 | actionPerformed: "scrollIntoView", 1637 | itemId: "12", 1638 | }); 1639 | 1640 | // Verify that the node and its parents are now expanded (target should be visible) 1641 | await expect(tree.getByTestId("10:scrollview")).toBeVisible(); // Root still visible 1642 | await expect(tree.getByTestId("11:scrollview")).toBeVisible(); // Parent expanded 1643 | await expect(tree.getByTestId("12:scrollview")).toBeVisible(); // Target node now visible 1644 | }); 1645 | 1646 | test("exposes refreshData method", async ({ 1647 | initTestBed, 1648 | createTreeDriver, 1649 | createButtonDriver, 1650 | }) => { 1651 | const { testStateDriver } = await initTestBed(` 1652 | <Fragment> 1653 | <VStack height="400px"> 1654 | <Tree id="treeApi" testId="tree" 1655 | dataFormat="flat" 1656 | defaultExpanded="all" 1657 | data='{${JSON.stringify(flatTreeData)}}'> 1658 | <property name="itemTemplate"> 1659 | <HStack testId="{$item.id}:refresh" verticalAlignment="center"> 1660 | <Text value="{$item.name}" /> 1661 | </HStack> 1662 | </property> 1663 | </Tree> 1664 | </VStack> 1665 | <Button id="refreshBtn" testId="refresh-btn" label="Refresh Data" onClick=" 1666 | treeApi.refreshData(); 1667 | testState = { actionPerformed: 'refreshData' }; 1668 | " /> 1669 | </Fragment> 1670 | `); 1671 | 1672 | const tree = await createTreeDriver("tree"); 1673 | const refreshButton = await createButtonDriver("refresh-btn"); 1674 | 1675 | // Verify tree is visible with original data 1676 | await expect(tree.getByTestId("1:refresh")).toBeVisible(); 1677 | await expect(tree.getByTestId("2:refresh")).toBeVisible(); 1678 | 1679 | // Click refresh data button 1680 | await refreshButton.click(); 1681 | 1682 | // Verify test state confirms action was performed 1683 | // Note: refreshData forces re-processing but with same data, tree should remain the same 1684 | await expect.poll(testStateDriver.testState).toEqual({ 1685 | actionPerformed: "refreshData", 1686 | }); 1687 | 1688 | // Tree should still be visible after refresh 1689 | await expect(tree.getByTestId("1:refresh")).toBeVisible(); 1690 | await expect(tree.getByTestId("2:refresh")).toBeVisible(); 1691 | }); 1692 | 1693 | // ============================================================================= 1694 | // COMPREHENSIVE API METHOD TESTS 1695 | // ============================================================================= 1696 | 1697 | test.describe("API Method Tests", () => { 1698 | test("expandAll() - expands all nodes and makes all children visible", async ({ 1699 | initTestBed, 1700 | createTreeDriver, 1701 | createButtonDriver, 1702 | }) => { 1703 | const { testStateDriver } = await initTestBed(` 1704 | <Fragment> 1705 | <VStack height="400px"> 1706 | <Tree id="treeApi" testId="tree" 1707 | dataFormat="hierarchy" 1708 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1709 | <property name="itemTemplate"> 1710 | <HStack testId="{$item.id}:expandall"> 1711 | <Text value="{$item.name}" /> 1712 | </HStack> 1713 | </property> 1714 | </Tree> 1715 | </VStack> 1716 | <Button testId="expandall-btn" label="Expand All" onClick=" 1717 | treeApi.expandAll(); 1718 | testState = { actionPerformed: 'expandAll' }; 1719 | " /> 1720 | </Fragment> 1721 | `); 1722 | 1723 | const tree = await createTreeDriver("tree"); 1724 | const expandAllButton = await createButtonDriver("expandall-btn"); 1725 | 1726 | // BEFORE: Verify tree starts collapsed - only root visible 1727 | await expect(tree.getByTestId("1:expandall")).toBeVisible(); // Root visible 1728 | await expect(tree.getByTestId("2:expandall")).not.toBeVisible(); // Child hidden 1729 | await expect(tree.getByTestId("3:expandall")).not.toBeVisible(); // Child hidden 1730 | 1731 | // Trigger expandAll API 1732 | await expandAllButton.click(); 1733 | 1734 | // Wait for async API call to complete 1735 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandAll" }); 1736 | 1737 | // AFTER: Verify all nodes are now visible 1738 | await expect(tree.getByTestId("1:expandall")).toBeVisible(); // Root still visible 1739 | await expect(tree.getByTestId("2:expandall")).toBeVisible(); // Child now visible 1740 | await expect(tree.getByTestId("3:expandall")).toBeVisible(); // Child now visible 1741 | 1742 | // Note: For hierarchyTreeData, we only have 2 levels, so all should be visible after expandAll 1743 | }); 1744 | 1745 | test("collapseAll() - collapses all nodes and hides all children", async ({ 1746 | initTestBed, 1747 | createTreeDriver, 1748 | createButtonDriver, 1749 | }) => { 1750 | const { testStateDriver } = await initTestBed(` 1751 | <Fragment> 1752 | <VStack height="400px"> 1753 | <Tree id="treeApi" testId="tree" 1754 | dataFormat="hierarchy" 1755 | defaultExpanded="all" 1756 | data='{${JSON.stringify(hierarchyTreeData)}}'> 1757 | <property name="itemTemplate"> 1758 | <HStack testId="{$item.id}:collapseall"> 1759 | <Text value="{$item.name}" /> 1760 | </HStack> 1761 | </property> 1762 | </Tree> 1763 | </VStack> 1764 | <Button testId="collapseall-btn" label="Collapse All" onClick=" 1765 | treeApi.collapseAll(); 1766 | testState = { actionPerformed: 'collapseAll' }; 1767 | " /> 1768 | </Fragment> 1769 | `); 1770 | 1771 | const tree = await createTreeDriver("tree"); 1772 | const collapseAllButton = await createButtonDriver("collapseall-btn"); 1773 | 1774 | // BEFORE: Verify tree starts expanded - all nodes visible 1775 | await expect(tree.getByTestId("1:collapseall")).toBeVisible(); // Root visible 1776 | await expect(tree.getByTestId("2:collapseall")).toBeVisible(); // Child visible 1777 | await expect(tree.getByTestId("3:collapseall")).toBeVisible(); // Child visible 1778 | 1779 | // Trigger collapseAll API 1780 | await collapseAllButton.click(); 1781 | 1782 | // Wait for async API call to complete 1783 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "collapseAll" }); 1784 | 1785 | // AFTER: Verify only root nodes are visible, children are hidden 1786 | await expect(tree.getByTestId("1:collapseall")).toBeVisible(); // Root still visible 1787 | await expect(tree.getByTestId("2:collapseall")).not.toBeVisible(); // Child now hidden 1788 | await expect(tree.getByTestId("3:collapseall")).not.toBeVisible(); // Child now hidden 1789 | }); 1790 | 1791 | test("expandAll() - with deep hierarchy (4+ levels)", async ({ 1792 | initTestBed, 1793 | createTreeDriver, 1794 | createButtonDriver, 1795 | }) => { 1796 | // Create a deeper hierarchy for thorough testing 1797 | const deepHierarchyData = [ 1798 | { 1799 | id: 1, 1800 | name: "Root Level 0", 1801 | children: [ 1802 | { 1803 | id: 2, 1804 | name: "Level 1 - Branch A", 1805 | children: [ 1806 | { 1807 | id: 3, 1808 | name: "Level 2 - Branch A.1", 1809 | children: [ 1810 | { id: 4, name: "Level 3 - Leaf A.1.1", children: [] }, 1811 | { id: 5, name: "Level 3 - Leaf A.1.2", children: [] }, 1812 | ], 1813 | }, 1814 | { id: 6, name: "Level 2 - Branch A.2", children: [] }, 1815 | ], 1816 | }, 1817 | { 1818 | id: 7, 1819 | name: "Level 1 - Branch B", 1820 | children: [{ id: 8, name: "Level 2 - Branch B.1", children: [] }], 1821 | }, 1822 | ], 1823 | }, 1824 | ]; 1825 | 1826 | const { testStateDriver } = await initTestBed(` 1827 | <Fragment> 1828 | <VStack height="400px"> 1829 | <Tree id="treeApi" testId="tree" 1830 | dataFormat="hierarchy" 1831 | data='{${JSON.stringify(deepHierarchyData)}}'> 1832 | <property name="itemTemplate"> 1833 | <HStack testId="{$item.id}:deep"> 1834 | <Text value="{$item.name}" /> 1835 | </HStack> 1836 | </property> 1837 | </Tree> 1838 | </VStack> 1839 | <Button testId="expandall-deep-btn" label="Expand All Deep" onClick=" 1840 | treeApi.expandAll(); 1841 | testState = { actionPerformed: 'expandAllDeep' }; 1842 | " /> 1843 | </Fragment> 1844 | `); 1845 | 1846 | const tree = await createTreeDriver("tree"); 1847 | const expandAllButton = await createButtonDriver("expandall-deep-btn"); 1848 | 1849 | // BEFORE: Verify tree starts collapsed - only root visible 1850 | await expect(tree.getByTestId("1:deep")).toBeVisible(); // Root (Level 0) 1851 | await expect(tree.getByTestId("2:deep")).not.toBeVisible(); // Level 1 - Branch A (hidden) 1852 | await expect(tree.getByTestId("3:deep")).not.toBeVisible(); // Level 2 - Branch A.1 (hidden) 1853 | await expect(tree.getByTestId("4:deep")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (hidden) 1854 | await expect(tree.getByTestId("7:deep")).not.toBeVisible(); // Level 1 - Branch B (hidden) 1855 | await expect(tree.getByTestId("8:deep")).not.toBeVisible(); // Level 2 - Branch B.1 (hidden) 1856 | 1857 | // Trigger expandAll API 1858 | await expandAllButton.click(); 1859 | 1860 | // Wait for async API call to complete 1861 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandAllDeep" }); 1862 | 1863 | // AFTER: Verify ALL levels are now visible (4 levels deep) 1864 | await expect(tree.getByTestId("1:deep")).toBeVisible(); // Root (Level 0) - still visible 1865 | await expect(tree.getByTestId("2:deep")).toBeVisible(); // Level 1 - Branch A (now visible) 1866 | await expect(tree.getByTestId("3:deep")).toBeVisible(); // Level 2 - Branch A.1 (now visible) 1867 | await expect(tree.getByTestId("4:deep")).toBeVisible(); // Level 3 - Leaf A.1.1 (now visible) 1868 | await expect(tree.getByTestId("5:deep")).toBeVisible(); // Level 3 - Leaf A.1.2 (now visible) 1869 | await expect(tree.getByTestId("6:deep")).toBeVisible(); // Level 2 - Branch A.2 (now visible) 1870 | await expect(tree.getByTestId("7:deep")).toBeVisible(); // Level 1 - Branch B (now visible) 1871 | await expect(tree.getByTestId("8:deep")).toBeVisible(); // Level 2 - Branch B.1 (now visible) 1872 | }); 1873 | 1874 | test("collapseAll() - with deep hierarchy (4+ levels)", async ({ 1875 | initTestBed, 1876 | createTreeDriver, 1877 | createButtonDriver, 1878 | }) => { 1879 | // Use the same deep hierarchy data 1880 | const deepHierarchyData = [ 1881 | { 1882 | id: 1, 1883 | name: "Root Level 0", 1884 | children: [ 1885 | { 1886 | id: 2, 1887 | name: "Level 1 - Branch A", 1888 | children: [ 1889 | { 1890 | id: 3, 1891 | name: "Level 2 - Branch A.1", 1892 | children: [ 1893 | { id: 4, name: "Level 3 - Leaf A.1.1", children: [] }, 1894 | { id: 5, name: "Level 3 - Leaf A.1.2", children: [] }, 1895 | ], 1896 | }, 1897 | { id: 6, name: "Level 2 - Branch A.2", children: [] }, 1898 | ], 1899 | }, 1900 | { 1901 | id: 7, 1902 | name: "Level 1 - Branch B", 1903 | children: [{ id: 8, name: "Level 2 - Branch B.1", children: [] }], 1904 | }, 1905 | ], 1906 | }, 1907 | ]; 1908 | 1909 | const { testStateDriver } = await initTestBed(` 1910 | <Fragment> 1911 | <VStack height="400px"> 1912 | <Tree id="treeApi" testId="tree" 1913 | dataFormat="hierarchy" 1914 | defaultExpanded="all" 1915 | data='{${JSON.stringify(deepHierarchyData)}}'> 1916 | <property name="itemTemplate"> 1917 | <HStack testId="{$item.id}:deepcollapse"> 1918 | <Text value="{$item.name}" /> 1919 | </HStack> 1920 | </property> 1921 | </Tree> 1922 | </VStack> 1923 | <Button testId="collapseall-deep-btn" label="Collapse All Deep" onClick=" 1924 | treeApi.collapseAll(); 1925 | testState = { actionPerformed: 'collapseAllDeep' }; 1926 | " /> 1927 | </Fragment> 1928 | `); 1929 | 1930 | const tree = await createTreeDriver("tree"); 1931 | const collapseAllButton = await createButtonDriver("collapseall-deep-btn"); 1932 | 1933 | // BEFORE: Verify tree starts fully expanded - all levels visible 1934 | await expect(tree.getByTestId("1:deepcollapse")).toBeVisible(); // Root (Level 0) 1935 | await expect(tree.getByTestId("2:deepcollapse")).toBeVisible(); // Level 1 - Branch A 1936 | await expect(tree.getByTestId("3:deepcollapse")).toBeVisible(); // Level 2 - Branch A.1 1937 | await expect(tree.getByTestId("4:deepcollapse")).toBeVisible(); // Level 3 - Leaf A.1.1 1938 | await expect(tree.getByTestId("5:deepcollapse")).toBeVisible(); // Level 3 - Leaf A.1.2 1939 | await expect(tree.getByTestId("7:deepcollapse")).toBeVisible(); // Level 1 - Branch B 1940 | await expect(tree.getByTestId("8:deepcollapse")).toBeVisible(); // Level 2 - Branch B.1 1941 | 1942 | // Trigger collapseAll API 1943 | await collapseAllButton.click(); 1944 | 1945 | // Wait for async API call to complete 1946 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "collapseAllDeep" }); 1947 | 1948 | // AFTER: Verify only root level nodes are visible, all children hidden 1949 | await expect(tree.getByTestId("1:deepcollapse")).toBeVisible(); // Root (Level 0) - still visible 1950 | await expect(tree.getByTestId("2:deepcollapse")).not.toBeVisible(); // Level 1 - Branch A (now hidden) 1951 | await expect(tree.getByTestId("3:deepcollapse")).not.toBeVisible(); // Level 2 - Branch A.1 (now hidden) 1952 | await expect(tree.getByTestId("4:deepcollapse")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (now hidden) 1953 | await expect(tree.getByTestId("5:deepcollapse")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (now hidden) 1954 | await expect(tree.getByTestId("6:deepcollapse")).not.toBeVisible(); // Level 2 - Branch A.2 (now hidden) 1955 | await expect(tree.getByTestId("7:deepcollapse")).not.toBeVisible(); // Level 1 - Branch B (now hidden) 1956 | await expect(tree.getByTestId("8:deepcollapse")).not.toBeVisible(); // Level 2 - Branch B.1 (now hidden) 1957 | }); 1958 | 1959 | test("expandToLevel(level) - expands nodes only to specified depth", async ({ 1960 | initTestBed, 1961 | createTreeDriver, 1962 | createButtonDriver, 1963 | }) => { 1964 | // Use deep hierarchy to test expandToLevel properly 1965 | const deepHierarchyData = [ 1966 | { 1967 | id: 1, 1968 | name: "Root Level 0", 1969 | children: [ 1970 | { 1971 | id: 2, 1972 | name: "Level 1 - Branch A", 1973 | children: [ 1974 | { 1975 | id: 3, 1976 | name: "Level 2 - Branch A.1", 1977 | children: [ 1978 | { id: 4, name: "Level 3 - Leaf A.1.1", children: [] }, 1979 | { id: 5, name: "Level 3 - Leaf A.1.2", children: [] }, 1980 | ], 1981 | }, 1982 | { id: 6, name: "Level 2 - Branch A.2", children: [] }, 1983 | ], 1984 | }, 1985 | { 1986 | id: 7, 1987 | name: "Level 1 - Branch B", 1988 | children: [{ id: 8, name: "Level 2 - Branch B.1", children: [] }], 1989 | }, 1990 | ], 1991 | }, 1992 | ]; 1993 | 1994 | const { testStateDriver } = await initTestBed(` 1995 | <Fragment> 1996 | <VStack height="400px"> 1997 | <Tree id="treeApi" testId="tree" 1998 | dataFormat="hierarchy" 1999 | data='{${JSON.stringify(deepHierarchyData)}}'> 2000 | <property name="itemTemplate"> 2001 | <HStack testId="{$item.id}:level"> 2002 | <Text value="{$item.name}" /> 2003 | </HStack> 2004 | </property> 2005 | </Tree> 2006 | </VStack> 2007 | <Button testId="expand-level0-btn" label="Expand to Level 0" onClick=" 2008 | treeApi.expandToLevel(0); 2009 | testState = { actionPerformed: 'expandToLevel0' }; 2010 | " /> 2011 | <Button testId="expand-level1-btn" label="Expand to Level 1" onClick=" 2012 | treeApi.expandToLevel(1); 2013 | testState = { actionPerformed: 'expandToLevel1' }; 2014 | " /> 2015 | <Button testId="expand-level2-btn" label="Expand to Level 2" onClick=" 2016 | treeApi.expandToLevel(2); 2017 | testState = { actionPerformed: 'expandToLevel2' }; 2018 | " /> 2019 | <Button testId="expand-level3-btn" label="Expand to Level 3" onClick=" 2020 | treeApi.expandToLevel(3); 2021 | testState = { actionPerformed: 'expandToLevel3' }; 2022 | " /> 2023 | </Fragment> 2024 | `); 2025 | 2026 | const tree = await createTreeDriver("tree"); 2027 | const expandLevel0Button = await createButtonDriver("expand-level0-btn"); 2028 | const expandLevel1Button = await createButtonDriver("expand-level1-btn"); 2029 | const expandLevel2Button = await createButtonDriver("expand-level2-btn"); 2030 | const expandLevel3Button = await createButtonDriver("expand-level3-btn"); 2031 | 2032 | // INITIAL STATE: Verify tree starts collapsed - only root visible 2033 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) 2034 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 (hidden) 2035 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 (hidden) 2036 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 (hidden) 2037 | 2038 | // TEST 1: expandToLevel(0) - should show only root level (no expansion) 2039 | await expandLevel0Button.click(); 2040 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandToLevel0" }); 2041 | 2042 | // AFTER expandToLevel(0): Only Level 0 visible 2043 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2044 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - Branch A (hidden) 2045 | await expect(tree.getByTestId("7:level")).not.toBeVisible(); // Level 1 - Branch B (hidden) 2046 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (hidden) 2047 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (hidden) 2048 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (hidden) 2049 | 2050 | // TEST 2: expandToLevel(1) - should show Level 0 and Level 1 only 2051 | await expandLevel1Button.click(); 2052 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandToLevel1" }); 2053 | 2054 | // AFTER expandToLevel(1): Level 0 and 1 visible, Level 2+ hidden 2055 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2056 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A (now visible) 2057 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B (now visible) 2058 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (still hidden) 2059 | await expect(tree.getByTestId("6:level")).not.toBeVisible(); // Level 2 - Branch A.2 (still hidden) 2060 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (still hidden) 2061 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden) 2062 | 2063 | // TEST 3: expandToLevel(2) - should show Level 0, 1, and 2 2064 | await expandLevel2Button.click(); 2065 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandToLevel2" }); 2066 | 2067 | // AFTER expandToLevel(2): Level 0, 1, and 2 visible, Level 3+ hidden 2068 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2069 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - visible 2070 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - visible 2071 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 (now visible) 2072 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 (now visible) 2073 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 (now visible) 2074 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden) 2075 | await expect(tree.getByTestId("5:level")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (still hidden) 2076 | 2077 | // TEST 4: expandToLevel(3) - should show all levels (0, 1, 2, and 3) 2078 | await expandLevel3Button.click(); 2079 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandToLevel3" }); 2080 | 2081 | // AFTER expandToLevel(3): All levels visible (complete expansion for this tree) 2082 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2083 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - visible 2084 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - visible 2085 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 - visible 2086 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 - visible 2087 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 - visible 2088 | await expect(tree.getByTestId("4:level")).toBeVisible(); // Level 3 - Leaf A.1.1 (now visible) 2089 | await expect(tree.getByTestId("5:level")).toBeVisible(); // Level 3 - Leaf A.1.2 (now visible) 2090 | }); 2091 | 2092 | test("expandNode(nodeId) - expands specific node and shows its children", async ({ 2093 | initTestBed, 2094 | createTreeDriver, 2095 | createButtonDriver, 2096 | }) => { 2097 | // Use deep hierarchy to test individual node expansion 2098 | const deepHierarchyData = [ 2099 | { 2100 | id: 1, 2101 | name: "Root Level 0", 2102 | children: [ 2103 | { 2104 | id: 2, 2105 | name: "Level 1 - Branch A", 2106 | children: [ 2107 | { 2108 | id: 3, 2109 | name: "Level 2 - Branch A.1", 2110 | children: [ 2111 | { id: 4, name: "Level 3 - Leaf A.1.1", children: [] }, 2112 | { id: 5, name: "Level 3 - Leaf A.1.2", children: [] }, 2113 | ], 2114 | }, 2115 | { id: 6, name: "Level 2 - Branch A.2", children: [] }, 2116 | ], 2117 | }, 2118 | { 2119 | id: 7, 2120 | name: "Level 1 - Branch B", 2121 | children: [{ id: 8, name: "Level 2 - Branch B.1", children: [] }], 2122 | }, 2123 | ], 2124 | }, 2125 | ]; 2126 | 2127 | const { testStateDriver } = await initTestBed(` 2128 | <Fragment> 2129 | <VStack height="400px"> 2130 | <Tree id="treeApi" testId="tree" 2131 | dataFormat="hierarchy" 2132 | data='{${JSON.stringify(deepHierarchyData)}}'> 2133 | <property name="itemTemplate"> 2134 | <HStack testId="{$item.id}:level"> 2135 | <Text value="{$item.name}" /> 2136 | </HStack> 2137 | </property> 2138 | </Tree> 2139 | </VStack> 2140 | <Button testId="expand-root-btn" label="Expand Root" onClick=" 2141 | treeApi.expandNode(1); 2142 | testState = { actionPerformed: 'expandRoot' }; 2143 | " /> 2144 | <Button testId="expand-node2-btn" label="Expand Node 2" onClick=" 2145 | treeApi.expandNode(2); 2146 | testState = { actionPerformed: 'expandNode2' }; 2147 | " /> 2148 | <Button testId="expand-node3-btn" label="Expand Node 3" onClick=" 2149 | treeApi.expandNode(3); 2150 | testState = { actionPerformed: 'expandNode3' }; 2151 | " /> 2152 | <Button testId="expand-node7-btn" label="Expand Node 7" onClick=" 2153 | treeApi.expandNode(7); 2154 | testState = { actionPerformed: 'expandNode7' }; 2155 | " /> 2156 | </Fragment> 2157 | `); 2158 | 2159 | const tree = await createTreeDriver("tree"); 2160 | const expandRootButton = await createButtonDriver("expand-root-btn"); 2161 | const expandNode2Button = await createButtonDriver("expand-node2-btn"); 2162 | const expandNode3Button = await createButtonDriver("expand-node3-btn"); 2163 | const expandNode7Button = await createButtonDriver("expand-node7-btn"); 2164 | 2165 | // INITIAL STATE: Verify tree starts collapsed - only root visible 2166 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2167 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - Branch A (hidden) 2168 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (hidden) 2169 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (hidden) 2170 | await expect(tree.getByTestId("6:level")).not.toBeVisible(); // Level 2 - Branch A.2 (hidden) 2171 | await expect(tree.getByTestId("7:level")).not.toBeVisible(); // Level 1 - Branch B (hidden) 2172 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (hidden) 2173 | 2174 | // FIRST: Expand root to make Level 1 nodes visible 2175 | await expandRootButton.click(); 2176 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandRoot" }); 2177 | 2178 | // AFTER expanding root: Level 1 nodes become visible 2179 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2180 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A (now visible) 2181 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B (now visible) 2182 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (still hidden) 2183 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden) 2184 | await expect(tree.getByTestId("6:level")).not.toBeVisible(); // Level 2 - Branch A.2 (still hidden) 2185 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (still hidden) 2186 | 2187 | // TEST 1: expandNode(2) - should expand "Level 1 - Branch A" and show its children 2188 | await expandNode2Button.click(); 2189 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandNode2" }); 2190 | 2191 | // AFTER expandNode(2): Node 2's children become visible, others stay hidden 2192 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2193 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A (still visible) 2194 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 (now visible - child of node 2) 2195 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 (now visible - child of node 2) 2196 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden - child of node 3) 2197 | await expect(tree.getByTestId("5:level")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (still hidden - child of node 3) 2198 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B (still visible from root expansion) 2199 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (still hidden - child of node 7) 2200 | 2201 | // TEST 2: expandNode(3) - should expand "Level 2 - Branch A.1" and show its children 2202 | await expandNode3Button.click(); 2203 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandNode3" }); 2204 | 2205 | // AFTER expandNode(3): Node 3's children become visible, previous expansions remain 2206 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2207 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - still visible 2208 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 - still visible 2209 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 - still visible 2210 | await expect(tree.getByTestId("4:level")).toBeVisible(); // Level 3 - Leaf A.1.1 (now visible - child of node 3) 2211 | await expect(tree.getByTestId("5:level")).toBeVisible(); // Level 3 - Leaf A.1.2 (now visible - child of node 3) 2212 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B (still visible from root expansion) 2213 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (still hidden - child of node 7) 2214 | 2215 | // TEST 3: expandNode(7) - should expand "Level 1 - Branch B" and show its children 2216 | await expandNode7Button.click(); 2217 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandNode7" }); 2218 | 2219 | // AFTER expandNode(7): Node 7's children become visible, all previous expansions remain 2220 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2221 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - still visible 2222 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 - still visible 2223 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 - still visible 2224 | await expect(tree.getByTestId("4:level")).toBeVisible(); // Level 3 - Leaf A.1.1 - still visible 2225 | await expect(tree.getByTestId("5:level")).toBeVisible(); // Level 3 - Leaf A.1.2 - still visible 2226 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - still visible 2227 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 (now visible - child of node 7) 2228 | }); 2229 | 2230 | test("expandNode(nodeId) - negative tests for invalid or inaccessible nodes", async ({ 2231 | initTestBed, 2232 | createTreeDriver, 2233 | createButtonDriver, 2234 | }) => { 2235 | // Use simple hierarchy to test negative cases clearly 2236 | const simpleHierarchyData = [ 2237 | { 2238 | id: 1, 2239 | name: "Root Level 0", 2240 | children: [ 2241 | { 2242 | id: 2, 2243 | name: "Level 1 - Branch A", 2244 | children: [{ id: 3, name: "Level 2 - Leaf A.1", children: [] }], 2245 | }, 2246 | ], 2247 | }, 2248 | ]; 2249 | 2250 | const { testStateDriver } = await initTestBed(` 2251 | <Fragment> 2252 | <VStack height="400px"> 2253 | <Tree id="treeApi" testId="tree" 2254 | dataFormat="hierarchy" 2255 | data='{${JSON.stringify(simpleHierarchyData)}}'> 2256 | <property name="itemTemplate"> 2257 | <HStack testId="{$item.id}:level"> 2258 | <Text value="{$item.name}" /> 2259 | </HStack> 2260 | </property> 2261 | </Tree> 2262 | </VStack> 2263 | <Button testId="expand-nonexistent-btn" label="Expand Non-existent Node" onClick=" 2264 | treeApi.expandNode(999); 2265 | testState = { actionPerformed: 'expandNonExistent' }; 2266 | " /> 2267 | <Button testId="expand-leaf-node3-btn" label="Expand Leaf Node 3" onClick=" 2268 | treeApi.expandNode(3); 2269 | testState = { actionPerformed: 'expandLeafNode3' }; 2270 | " /> 2271 | <Button testId="expand-root-btn" label="Expand Root" onClick=" 2272 | treeApi.expandNode(1); 2273 | testState = { actionPerformed: 'expandRoot' }; 2274 | " /> 2275 | <Button testId="expand-node2-btn" label="Expand Node 2" onClick=" 2276 | treeApi.expandNode(2); 2277 | testState = { actionPerformed: 'expandNode2' }; 2278 | " /> 2279 | </Fragment> 2280 | `); 2281 | 2282 | const tree = await createTreeDriver("tree"); 2283 | const expandNonExistentButton = await createButtonDriver("expand-nonexistent-btn"); 2284 | const expandLeafNode3Button = await createButtonDriver("expand-leaf-node3-btn"); 2285 | const expandRootButton = await createButtonDriver("expand-root-btn"); 2286 | const expandNode2Button = await createButtonDriver("expand-node2-btn"); 2287 | 2288 | // INITIAL STATE: Only root visible, all children hidden 2289 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - visible 2290 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - hidden 2291 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - hidden 2292 | 2293 | // NEGATIVE TEST 1: Try to expand non-existent node (ID 999) 2294 | await expandNonExistentButton.click(); 2295 | await expect 2296 | .poll(testStateDriver.testState) 2297 | .toEqual({ actionPerformed: "expandNonExistent" }); 2298 | 2299 | // AFTER expandNode(999): Should have no effect, tree state unchanged 2300 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - still visible 2301 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - still hidden 2302 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - still hidden 2303 | 2304 | // NEGATIVE TEST 2: Try to expand leaf node 3 (which has no children) while it's hidden 2305 | await expandLeafNode3Button.click(); 2306 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandLeafNode3" }); 2307 | 2308 | // AFTER expandNode(3) on hidden leaf: Should have no effect since node is not visible 2309 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - still visible 2310 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - still hidden 2311 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - still hidden 2312 | 2313 | // Now expand the tree properly to make nodes visible 2314 | await expandRootButton.click(); 2315 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandRoot" }); 2316 | 2317 | await expandNode2Button.click(); 2318 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandNode2" }); 2319 | 2320 | // After proper expansion: All nodes should be visible 2321 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - visible 2322 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - visible 2323 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - visible 2324 | 2325 | // NEGATIVE TEST 3: Try to expand leaf node 3 again (now that it's visible but still has no children) 2326 | await expandLeafNode3Button.click(); 2327 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "expandLeafNode3" }); 2328 | 2329 | // AFTER expandNode(3) on visible leaf: Should have no visible effect since leaf nodes can't expand 2330 | // Tree state should remain the same - all nodes still visible 2331 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - still visible 2332 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - still visible 2333 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - still visible 2334 | }); 2335 | 2336 | test("collapseNode(nodeId) - collapses specific node and hides its children", async ({ 2337 | initTestBed, 2338 | createTreeDriver, 2339 | createButtonDriver, 2340 | }) => { 2341 | // Use deep hierarchy to test individual node collapse 2342 | const deepHierarchyData = [ 2343 | { 2344 | id: 1, 2345 | name: "Root Level 0", 2346 | children: [ 2347 | { 2348 | id: 2, 2349 | name: "Level 1 - Branch A", 2350 | children: [ 2351 | { 2352 | id: 3, 2353 | name: "Level 2 - Branch A.1", 2354 | children: [ 2355 | { id: 4, name: "Level 3 - Leaf A.1.1", children: [] }, 2356 | { id: 5, name: "Level 3 - Leaf A.1.2", children: [] }, 2357 | ], 2358 | }, 2359 | { id: 6, name: "Level 2 - Branch A.2", children: [] }, 2360 | ], 2361 | }, 2362 | { 2363 | id: 7, 2364 | name: "Level 1 - Branch B", 2365 | children: [{ id: 8, name: "Level 2 - Branch B.1", children: [] }], 2366 | }, 2367 | ], 2368 | }, 2369 | ]; 2370 | 2371 | const { testStateDriver } = await initTestBed(` 2372 | <Fragment> 2373 | <VStack height="400px"> 2374 | <Tree id="treeApi" testId="tree" 2375 | dataFormat="hierarchy" 2376 | defaultExpanded="all" 2377 | data='{${JSON.stringify(deepHierarchyData)}}'> 2378 | <property name="itemTemplate"> 2379 | <HStack testId="{$item.id}:level"> 2380 | <Text value="{$item.name}" /> 2381 | </HStack> 2382 | </property> 2383 | </Tree> 2384 | </VStack> 2385 | <Button testId="collapse-node3-btn" label="Collapse Node 3" onClick=" 2386 | treeApi.collapseNode(3); 2387 | testState = { actionPerformed: 'collapseNode3' }; 2388 | " /> 2389 | <Button testId="collapse-node2-btn" label="Collapse Node 2" onClick=" 2390 | treeApi.collapseNode(2); 2391 | testState = { actionPerformed: 'collapseNode2' }; 2392 | " /> 2393 | <Button testId="collapse-root-btn" label="Collapse Root" onClick=" 2394 | treeApi.collapseNode(1); 2395 | testState = { actionPerformed: 'collapseRoot' }; 2396 | " /> 2397 | </Fragment> 2398 | `); 2399 | 2400 | const tree = await createTreeDriver("tree"); 2401 | const collapseNode3Button = await createButtonDriver("collapse-node3-btn"); 2402 | const collapseNode2Button = await createButtonDriver("collapse-node2-btn"); 2403 | const collapseRootButton = await createButtonDriver("collapse-root-btn"); 2404 | 2405 | // INITIAL STATE: Tree starts fully expanded - all nodes visible 2406 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - visible 2407 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - visible 2408 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 - visible 2409 | await expect(tree.getByTestId("4:level")).toBeVisible(); // Level 3 - Leaf A.1.1 - visible 2410 | await expect(tree.getByTestId("5:level")).toBeVisible(); // Level 3 - Leaf A.1.2 - visible 2411 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 - visible 2412 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - visible 2413 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 - visible 2414 | 2415 | // TEST 1: collapseNode(3) - should collapse "Level 2 - Branch A.1" and hide its children 2416 | await collapseNode3Button.click(); 2417 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "collapseNode3" }); 2418 | 2419 | // AFTER collapseNode(3): Node 3's children become hidden, others remain visible 2420 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2421 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - still visible 2422 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - Branch A.1 - still visible (but collapsed) 2423 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (now hidden - child of collapsed node 3) 2424 | await expect(tree.getByTestId("5:level")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (now hidden - child of collapsed node 3) 2425 | await expect(tree.getByTestId("6:level")).toBeVisible(); // Level 2 - Branch A.2 - still visible (not child of node 3) 2426 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - still visible 2427 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 - still visible 2428 | 2429 | // TEST 2: collapseNode(2) - should collapse "Level 1 - Branch A" and hide all its descendants 2430 | await collapseNode2Button.click(); 2431 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "collapseNode2" }); 2432 | 2433 | // AFTER collapseNode(2): Node 2's entire subtree becomes hidden 2434 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible 2435 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - Branch A - still visible (but collapsed) 2436 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (now hidden - child of collapsed node 2) 2437 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden) 2438 | await expect(tree.getByTestId("5:level")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (still hidden) 2439 | await expect(tree.getByTestId("6:level")).not.toBeVisible(); // Level 2 - Branch A.2 (now hidden - child of collapsed node 2) 2440 | await expect(tree.getByTestId("7:level")).toBeVisible(); // Level 1 - Branch B - still visible (not descendant of node 2) 2441 | await expect(tree.getByTestId("8:level")).toBeVisible(); // Level 2 - Branch B.1 - still visible 2442 | 2443 | // TEST 3: collapseNode(1) - should collapse root and hide all children 2444 | await collapseRootButton.click(); 2445 | await expect.poll(testStateDriver.testState).toEqual({ actionPerformed: "collapseRoot" }); 2446 | 2447 | // AFTER collapseNode(1): All children of root become hidden 2448 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Level 0 (Root) - still visible (but collapsed) 2449 | await expect(tree.getByTestId("2:level")).not.toBeVisible(); // Level 1 - Branch A (now hidden - child of collapsed root) 2450 | await expect(tree.getByTestId("3:level")).not.toBeVisible(); // Level 2 - Branch A.1 (still hidden) 2451 | await expect(tree.getByTestId("4:level")).not.toBeVisible(); // Level 3 - Leaf A.1.1 (still hidden) 2452 | await expect(tree.getByTestId("5:level")).not.toBeVisible(); // Level 3 - Leaf A.1.2 (still hidden) 2453 | await expect(tree.getByTestId("6:level")).not.toBeVisible(); // Level 2 - Branch A.2 (still hidden) 2454 | await expect(tree.getByTestId("7:level")).not.toBeVisible(); // Level 1 - Branch B (now hidden - child of collapsed root) 2455 | await expect(tree.getByTestId("8:level")).not.toBeVisible(); // Level 2 - Branch B.1 (now hidden) 2456 | }); 2457 | 2458 | test("collapseNode(nodeId) - negative tests for invalid or leaf nodes", async ({ 2459 | initTestBed, 2460 | createTreeDriver, 2461 | createButtonDriver, 2462 | }) => { 2463 | // Use simple hierarchy for negative tests 2464 | const simpleHierarchyData = [ 2465 | { 2466 | id: 1, 2467 | name: "Root Level 0", 2468 | children: [ 2469 | { 2470 | id: 2, 2471 | name: "Level 1 - Branch A", 2472 | children: [{ id: 3, name: "Level 2 - Leaf A.1", children: [] }], 2473 | }, 2474 | ], 2475 | }, 2476 | ]; 2477 | 2478 | const { testStateDriver } = await initTestBed(` 2479 | <Fragment> 2480 | <VStack height="400px"> 2481 | <Tree id="treeApi" testId="tree" 2482 | dataFormat="hierarchy" 2483 | defaultExpanded="all" 2484 | data='{${JSON.stringify(simpleHierarchyData)}}'> 2485 | <property name="itemTemplate"> 2486 | <HStack testId="{$item.id}:level"> 2487 | <Text value="{$item.name}" /> 2488 | </HStack> 2489 | </property> 2490 | </Tree> 2491 | </VStack> 2492 | <Button testId="collapse-nonexistent-btn" label="Collapse Non-existent Node" onClick=" 2493 | treeApi.collapseNode(999); 2494 | testState = { actionPerformed: 'collapseNonExistent' }; 2495 | " /> 2496 | <Button testId="collapse-leaf-node3-btn" label="Collapse Leaf Node 3" onClick=" 2497 | treeApi.collapseNode(3); 2498 | testState = { actionPerformed: 'collapseLeafNode3' }; 2499 | " /> 2500 | </Fragment> 2501 | `); 2502 | 2503 | const tree = await createTreeDriver("tree"); 2504 | const collapseNonExistentButton = await createButtonDriver("collapse-nonexistent-btn"); 2505 | const collapseLeafNode3Button = await createButtonDriver("collapse-leaf-node3-btn"); 2506 | 2507 | // INITIAL STATE: Tree starts fully expanded 2508 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - visible 2509 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - visible 2510 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - visible 2511 | 2512 | // NEGATIVE TEST 1: Try to collapse non-existent node (ID 999) 2513 | await collapseNonExistentButton.click(); 2514 | await expect 2515 | .poll(testStateDriver.testState) 2516 | .toEqual({ actionPerformed: "collapseNonExistent" }); 2517 | 2518 | // AFTER collapseNode(999): Should have no effect, tree state unchanged 2519 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - still visible 2520 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - still visible 2521 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - still visible 2522 | 2523 | // NEGATIVE TEST 2: Try to collapse leaf node 3 (which has no children to hide) 2524 | await collapseLeafNode3Button.click(); 2525 | await expect 2526 | .poll(testStateDriver.testState) 2527 | .toEqual({ actionPerformed: "collapseLeafNode3" }); 2528 | 2529 | // AFTER collapseNode(3) on leaf: Should have no visible effect since leaf nodes have no children 2530 | // Tree state should remain the same 2531 | await expect(tree.getByTestId("1:level")).toBeVisible(); // Root - still visible 2532 | await expect(tree.getByTestId("2:level")).toBeVisible(); // Level 1 - still visible 2533 | await expect(tree.getByTestId("3:level")).toBeVisible(); // Level 2 - still visible 2534 | }); 2535 | 2536 | test("selectNode(nodeId) - API method executes without error", async ({ 2537 | page, 2538 | initTestBed, 2539 | createTreeDriver, 2540 | createButtonDriver, 2541 | }) => { 2542 | // Use hierarchy for testing selectNode API 2543 | const selectableHierarchyData = [ 2544 | { 2545 | id: 1, 2546 | name: "Root Level 0", 2547 | children: [ 2548 | { 2549 | id: 2, 2550 | name: "Level 1 - Branch A", 2551 | children: [{ id: 3, name: "Level 2 - Leaf A.1", children: [] }], 2552 | }, 2553 | ], 2554 | }, 2555 | ]; 2556 | 2557 | await initTestBed(` 2558 | <Fragment> 2559 | <VStack height="400px" var.selectedNodeId="{null}"> 2560 | <Text testId="selectedId">{selectedNodeId}</Text> 2561 | <Tree id="treeApi" testId="tree" 2562 | dataFormat="hierarchy" 2563 | defaultExpanded="all" 2564 | onSelectionDidChange="node => {selectedNodeId = node.newNode.id}" 2565 | data='{${JSON.stringify(selectableHierarchyData)}}'> 2566 | <property name="itemTemplate"> 2567 | <HStack testId="{$item.id}:selection"> 2568 | <Text value="{$item.name}" /> 2569 | </HStack> 2570 | </property> 2571 | </Tree> 2572 | </VStack> 2573 | <Button testId="select-node2-btn" label="Select Node 2" onClick="treeApi.selectNode(2);" /> 2574 | <Button testId="select-nonexistent-btn" label="Select Non-existent Node" onClick=" 2575 | treeApi.selectNode('999'); 2576 | const selectedNode = treeApi.getSelectedNode(); 2577 | testState = { actionPerformed: 'selectNonExistent', selectedNodeData: selectedNode }; 2578 | " /> 2579 | </Fragment> 2580 | `); 2581 | 2582 | const tree = await createTreeDriver("tree"); 2583 | const selectNode2Button = await createButtonDriver("select-node2-btn"); 2584 | const selectNonExistentButton = await createButtonDriver("select-nonexistent-btn"); 2585 | const selectedIdText = page.getByTestId("selectedId"); 2586 | 2587 | // INITIAL STATE: No selection 2588 | await expect(selectedIdText).toHaveText(""); 2589 | 2590 | // TEST 1: selectNode('2') API call completes without error 2591 | await selectNode2Button.click(); 2592 | await expect(selectedIdText).toHaveText("2"); 2593 | 2594 | // TEST 2: selectNode('999') with invalid ID completes without error 2595 | await selectNonExistentButton.click(); 2596 | await expect(selectedIdText).toHaveText(""); 2597 | }); 2598 | 2599 | test("getSelectedNode() - returns correct selected node data", async ({ 2600 | initTestBed, 2601 | createTreeDriver, 2602 | createButtonDriver, 2603 | }) => { 2604 | // Simple hierarchy for testing getSelectedNode API 2605 | const simpleHierarchyData = [ 2606 | { 2607 | id: 1, 2608 | name: "Root Level 0", 2609 | children: [ 2610 | { id: 2, name: "Level 1 - Branch A", children: [] }, 2611 | { id: 3, name: "Level 1 - Branch B", children: [] }, 2612 | ], 2613 | }, 2614 | ]; 2615 | 2616 | const { testStateDriver } = await initTestBed(` 2617 | <Fragment> 2618 | <VStack height="400px"> 2619 | <Tree id="treeApi" testId="tree" 2620 | dataFormat="hierarchy" 2621 | defaultExpanded="all" 2622 | selectedValue="{2}" 2623 | data='{${JSON.stringify(simpleHierarchyData)}}'> 2624 | <property name="itemTemplate"> 2625 | <HStack testId="{$item.id}:selection"> 2626 | <Text value="{$item.name}" /> 2627 | </HStack> 2628 | </property> 2629 | </Tree> 2630 | </VStack> 2631 | <Button testId="get-selected-btn" label="Get Selected Node" onClick=" 2632 | const selectedNode = treeApi.getSelectedNode(); 2633 | testState = { 2634 | actionPerformed: 'getSelected', 2635 | selectedNodeData: selectedNode, 2636 | selectedKey: selectedNode?.key, 2637 | selectedName: selectedNode?.name 2638 | }; 2639 | " /> 2640 | <Button testId="get-selected-null-btn" label="Get Selected When None" onClick=" 2641 | const selectedNode = treeApi.getSelectedNode(); 2642 | testState = { 2643 | actionPerformed: 'getSelectedNull', 2644 | selectedNodeData: selectedNode 2645 | }; 2646 | " /> 2647 | </Fragment> 2648 | `); 2649 | 2650 | const getSelectedButton = await createButtonDriver("get-selected-btn"); 2651 | 2652 | // TEST 1: getSelectedNode() returns correct node when selectedValue is set 2653 | await getSelectedButton.click(); 2654 | await expect 2655 | .poll(async () => { 2656 | const state = await testStateDriver.testState(); 2657 | return state.actionPerformed; 2658 | }) 2659 | .toBe("getSelected"); 2660 | 2661 | // Verify getSelectedNode returns the correct node data 2662 | await expect 2663 | .poll(async () => { 2664 | const state = await testStateDriver.testState(); 2665 | return state.selectedKey; 2666 | }) 2667 | .toBe(2); 2668 | 2669 | await expect 2670 | .poll(async () => { 2671 | const state = await testStateDriver.testState(); 2672 | return state.selectedName; 2673 | }) 2674 | .toBe("Level 1 - Branch A"); 2675 | 2676 | // Verify the returned node is not null 2677 | await expect 2678 | .poll(async () => { 2679 | const state = await testStateDriver.testState(); 2680 | return state.selectedNodeData; 2681 | }) 2682 | .not.toBe(null); 2683 | 2684 | // TEST 2: Test with no selection (update tree to have no selectedValue) 2685 | const { testStateDriver: testStateDriver2 } = await initTestBed(` 2686 | <Fragment> 2687 | <VStack height="400px"> 2688 | <Tree id="treeApi" testId="tree" 2689 | dataFormat="hierarchy" 2690 | defaultExpanded="all" 2691 | data='{${JSON.stringify(simpleHierarchyData)}}'> 2692 | <property name="itemTemplate"> 2693 | <HStack testId="{$item.id}:selection"> 2694 | <Text value="{$item.name}" /> 2695 | </HStack> 2696 | </property> 2697 | </Tree> 2698 | </VStack> 2699 | <Button testId="get-selected-null-btn" label="Get Selected When None" onClick=" 2700 | const selectedNode = treeApi.getSelectedNode(); 2701 | testState = { 2702 | actionPerformed: 'getSelectedNull', 2703 | selectedNodeData: selectedNode 2704 | }; 2705 | " /> 2706 | </Fragment> 2707 | `); 2708 | 2709 | const getSelectedNullButton = await createButtonDriver("get-selected-null-btn"); 2710 | 2711 | await getSelectedNullButton.click(); 2712 | await expect 2713 | .poll(async () => { 2714 | const state = await testStateDriver2.testState(); 2715 | return state.actionPerformed; 2716 | }) 2717 | .toBe("getSelectedNull"); 2718 | 2719 | // Verify getSelectedNode returns null when no selection 2720 | await expect 2721 | .poll(async () => { 2722 | const state = await testStateDriver2.testState(); 2723 | return state.selectedNodeData; 2724 | }) 2725 | .toBe(null); 2726 | }); 2727 | 2728 | test("clearSelection() - API method executes without error", async ({ 2729 | initTestBed, 2730 | createTreeDriver, 2731 | createButtonDriver, 2732 | }) => { 2733 | // Simple hierarchy for testing clearSelection API 2734 | const simpleHierarchyData = [ 2735 | { 2736 | id: 1, 2737 | name: "Root Level 0", 2738 | children: [{ id: 2, name: "Level 1 - Branch A", children: [] }], 2739 | }, 2740 | ]; 2741 | 2742 | const { testStateDriver } = await initTestBed(` 2743 | <Fragment> 2744 | <VStack height="400px"> 2745 | <Tree id="treeApi" testId="tree" 2746 | dataFormat="hierarchy" 2747 | defaultExpanded="all" 2748 | data='{${JSON.stringify(simpleHierarchyData)}}'> 2749 | <property name="itemTemplate"> 2750 | <HStack testId="{$item.id}:clear"> 2751 | <Text value="{$item.name}" /> 2752 | </HStack> 2753 | </property> 2754 | </Tree> 2755 | </VStack> 2756 | <Button testId="clear-selection-btn" label="Clear Selection" onClick=" 2757 | try { 2758 | treeApi.clearSelection(); 2759 | testState = { 2760 | actionPerformed: 'clearSelection', 2761 | success: true, 2762 | error: null 2763 | }; 2764 | } catch (error) { 2765 | testState = { 2766 | actionPerformed: 'clearSelection', 2767 | success: false, 2768 | error: error.message 2769 | }; 2770 | } 2771 | " /> 2772 | <Button testId="get-selected-btn" label="Get Selected Node" onClick=" 2773 | try { 2774 | const selectedNode = treeApi.getSelectedNode(); 2775 | testState = { 2776 | actionPerformed: 'getSelected', 2777 | success: true, 2778 | selectedNodeData: selectedNode, 2779 | error: null 2780 | }; 2781 | } catch (error) { 2782 | testState = { 2783 | actionPerformed: 'getSelected', 2784 | success: false, 2785 | error: error.message 2786 | }; 2787 | } 2788 | " /> 2789 | </Fragment> 2790 | `); 2791 | 2792 | const clearSelectionButton = await createButtonDriver("clear-selection-btn"); 2793 | const getSelectedButton = await createButtonDriver("get-selected-btn"); 2794 | 2795 | // TEST: clearSelection() API call completes without error 2796 | await clearSelectionButton.click(); 2797 | await expect 2798 | .poll(async () => { 2799 | const state = await testStateDriver.testState(); 2800 | return state.actionPerformed; 2801 | }) 2802 | .toBe("clearSelection"); 2803 | 2804 | let currentState = await testStateDriver.testState(); 2805 | expect(currentState.success).toBe(true); 2806 | expect(currentState.error).toBe(null); 2807 | 2808 | // TEST: getSelectedNode() API call completes without error 2809 | await getSelectedButton.click(); 2810 | await expect 2811 | .poll(async () => { 2812 | const state = await testStateDriver.testState(); 2813 | return state.actionPerformed; 2814 | }) 2815 | .toBe("getSelected"); 2816 | 2817 | currentState = await testStateDriver.testState(); 2818 | expect(currentState.success).toBe(true); 2819 | expect(currentState.error).toBe(null); 2820 | // When no selection is managed, getSelectedNode() returns null 2821 | expect(currentState.selectedNodeData).toBe(null); 2822 | }); 2823 | 2824 | test("getNodeById() - returns correct node data or null for invalid keys", async ({ 2825 | initTestBed, 2826 | createTreeDriver, 2827 | createButtonDriver, 2828 | }) => { 2829 | // Rich hierarchy data for testing getNodeById API 2830 | const hierarchyData = [ 2831 | { 2832 | id: 1, 2833 | name: "Root Level 0", 2834 | children: [ 2835 | { 2836 | id: 2, 2837 | name: "Level 1 - Branch A", 2838 | children: [ 2839 | { id: 4, name: "Level 2 - Leaf A1", children: [] }, 2840 | { id: 5, name: "Level 2 - Leaf A2", children: [] }, 2841 | ], 2842 | }, 2843 | { id: 3, name: "Level 1 - Branch B", children: [] }, 2844 | ], 2845 | }, 2846 | { 2847 | id: 6, 2848 | name: "Root Level 1", 2849 | children: [{ id: 7, name: "Level 1 - Branch C", children: [] }], 2850 | }, 2851 | ]; 2852 | 2853 | const { testStateDriver } = await initTestBed(` 2854 | <Fragment> 2855 | <VStack height="400px"> 2856 | <Tree id="treeApi" testId="tree" 2857 | dataFormat="hierarchy" 2858 | defaultExpanded="all" 2859 | data='{${JSON.stringify(hierarchyData)}}'> 2860 | <property name="itemTemplate"> 2861 | <HStack testId="{$item.id}:getById"> 2862 | <Text value="{$item.name}" /> 2863 | </HStack> 2864 | </property> 2865 | </Tree> 2866 | </VStack> 2867 | <Button testId="get-node1-btn" label="Get Node 1" onClick=" 2868 | const node = treeApi.getNodeById(1); 2869 | testState = { 2870 | actionPerformed: 'getNode1', 2871 | nodeData: node, 2872 | nodeKey: node?.key, 2873 | nodeName: node?.name 2874 | }; 2875 | " /> 2876 | <Button testId="get-node4-btn" label="Get Node 4" onClick=" 2877 | const node = treeApi.getNodeById(4); 2878 | testState = { 2879 | actionPerformed: 'getNode4', 2880 | nodeData: node, 2881 | nodeKey: node?.key, 2882 | nodeName: node?.name 2883 | }; 2884 | " /> 2885 | <Button testId="get-node7-btn" label="Get Node 7" onClick=" 2886 | const node = treeApi.getNodeById(7); 2887 | testState = { 2888 | actionPerformed: 'getNode7', 2889 | nodeData: node, 2890 | nodeKey: node?.key, 2891 | nodeName: node?.name 2892 | }; 2893 | " /> 2894 | <Button testId="get-invalid-btn" label="Get Invalid Node" onClick=" 2895 | const node = treeApi.getNodeById(999); 2896 | testState = { 2897 | actionPerformed: 'getInvalid', 2898 | nodeData: node, 2899 | nodeExists: node !== null 2900 | }; 2901 | " /> 2902 | </Fragment> 2903 | `); 2904 | 2905 | const getNode1Button = await createButtonDriver("get-node1-btn"); 2906 | const getNode4Button = await createButtonDriver("get-node4-btn"); 2907 | const getNode7Button = await createButtonDriver("get-node7-btn"); 2908 | const getInvalidButton = await createButtonDriver("get-invalid-btn"); 2909 | 2910 | // TEST: getNodeById(1) should return root node data 2911 | await getNode1Button.click(); 2912 | await expect 2913 | .poll(async () => { 2914 | const state = await testStateDriver.testState(); 2915 | return state.actionPerformed; 2916 | }) 2917 | .toBe("getNode1"); 2918 | 2919 | let currentState = await testStateDriver.testState(); 2920 | expect(currentState.nodeKey).toBe(1); 2921 | expect(currentState.nodeName).toBe("Root Level 0"); 2922 | expect(currentState.nodeData).not.toBe(null); 2923 | 2924 | // TEST: getNodeById(4) should return deep nested node data 2925 | await getNode4Button.click(); 2926 | await expect 2927 | .poll(async () => { 2928 | const state = await testStateDriver.testState(); 2929 | return state.actionPerformed; 2930 | }) 2931 | .toBe("getNode4"); 2932 | 2933 | currentState = await testStateDriver.testState(); 2934 | expect(currentState.nodeKey).toBe(4); 2935 | expect(currentState.nodeName).toBe("Level 2 - Leaf A1"); 2936 | expect(currentState.nodeData).not.toBe(null); 2937 | 2938 | // TEST: getNodeById(7) should return node from second root 2939 | await getNode7Button.click(); 2940 | await expect 2941 | .poll(async () => { 2942 | const state = await testStateDriver.testState(); 2943 | return state.actionPerformed; 2944 | }) 2945 | .toBe("getNode7"); 2946 | 2947 | currentState = await testStateDriver.testState(); 2948 | expect(currentState.nodeKey).toBe(7); 2949 | expect(currentState.nodeName).toBe("Level 1 - Branch C"); 2950 | expect(currentState.nodeData).not.toBe(null); 2951 | 2952 | // TEST: getNodeById(999) should return null for non-existent node 2953 | await getInvalidButton.click(); 2954 | await expect 2955 | .poll(async () => { 2956 | const state = await testStateDriver.testState(); 2957 | return state.actionPerformed; 2958 | }) 2959 | .toBe("getInvalid"); 2960 | 2961 | currentState = await testStateDriver.testState(); 2962 | expect(currentState.nodeExists).toBe(false); 2963 | expect(currentState.nodeData).toBe(null); 2964 | }); 2965 | 2966 | test("getNodeById() - can retrieve nodes within collapsed parents", async ({ 2967 | initTestBed, 2968 | createTreeDriver, 2969 | createButtonDriver, 2970 | }) => { 2971 | // Hierarchy with nested structure to test collapsed nodes 2972 | const hierarchyData = [ 2973 | { 2974 | id: 1, 2975 | name: "Root Level 0", 2976 | children: [ 2977 | { 2978 | id: 2, 2979 | name: "Level 1 - Branch A", 2980 | children: [ 2981 | { id: 4, name: "Level 2 - Hidden Leaf A1", children: [] }, 2982 | { id: 5, name: "Level 2 - Hidden Leaf A2", children: [] }, 2983 | ], 2984 | }, 2985 | { id: 3, name: "Level 1 - Branch B", children: [] }, 2986 | ], 2987 | }, 2988 | ]; 2989 | 2990 | const { testStateDriver } = await initTestBed(` 2991 | <Fragment> 2992 | <VStack height="400px"> 2993 | <Tree id="treeApi" testId="tree" 2994 | dataFormat="hierarchy" 2995 | defaultExpanded="all" 2996 | data='{${JSON.stringify(hierarchyData)}}'> 2997 | <property name="itemTemplate"> 2998 | <HStack testId="{$item.id}:hidden"> 2999 | <Text value="{$item.name}" /> 3000 | </HStack> 3001 | </property> 3002 | </Tree> 3003 | </VStack> 3004 | <Button testId="collapse-node2-btn" label="Collapse Node 2" onClick=" 3005 | treeApi.collapseNode(2); 3006 | testState = { actionPerformed: 'collapseNode2' }; 3007 | " /> 3008 | <Button testId="get-visible-node1-btn" label="Get Visible Node 1" onClick=" 3009 | const node = treeApi.getNodeById(1); 3010 | testState = { 3011 | actionPerformed: 'getVisibleNode1', 3012 | nodeData: node, 3013 | nodeKey: node?.key, 3014 | nodeName: node?.name, 3015 | nodeExists: node !== null 3016 | }; 3017 | " /> 3018 | <Button testId="get-hidden-node4-btn" label="Get Hidden Node 4" onClick=" 3019 | const node = treeApi.getNodeById(4); 3020 | testState = { 3021 | actionPerformed: 'getHiddenNode4', 3022 | nodeData: node, 3023 | nodeKey: node?.key, 3024 | nodeName: node?.name, 3025 | nodeExists: node !== null 3026 | }; 3027 | " /> 3028 | <Button testId="get-hidden-node5-btn" label="Get Hidden Node 5" onClick=" 3029 | const node = treeApi.getNodeById(5); 3030 | testState = { 3031 | actionPerformed: 'getHiddenNode5', 3032 | nodeData: node, 3033 | nodeKey: node?.key, 3034 | nodeName: node?.name, 3035 | nodeExists: node !== null 3036 | }; 3037 | " /> 3038 | <Button testId="expand-node2-btn" label="Expand Node 2" onClick=" 3039 | treeApi.expandNode(2); 3040 | testState = { actionPerformed: 'expandNode2' }; 3041 | " /> 3042 | <Button testId="get-now-visible-node4-btn" label="Get Now Visible Node 4" onClick=" 3043 | const node = treeApi.getNodeById(4); 3044 | testState = { 3045 | actionPerformed: 'getNowVisibleNode4', 3046 | nodeData: node, 3047 | nodeKey: node?.key, 3048 | nodeName: node?.name, 3049 | nodeExists: node !== null 3050 | }; 3051 | " /> 3052 | </Fragment> 3053 | `); 3054 | 3055 | const tree = await createTreeDriver("tree"); 3056 | const collapseNode2Button = await createButtonDriver("collapse-node2-btn"); 3057 | const getVisibleNode1Button = await createButtonDriver("get-visible-node1-btn"); 3058 | const getHiddenNode4Button = await createButtonDriver("get-hidden-node4-btn"); 3059 | const getHiddenNode5Button = await createButtonDriver("get-hidden-node5-btn"); 3060 | const expandNode2Button = await createButtonDriver("expand-node2-btn"); 3061 | const getNowVisibleNode4Button = await createButtonDriver("get-now-visible-node4-btn"); 3062 | 3063 | // INITIAL STATE: defaultExpanded="all", so all nodes are visible 3064 | // First, verify all nodes are visible initially 3065 | const node1Wrapper = tree.getNodeWrapperByTestId("1:hidden"); 3066 | const node2Wrapper = tree.getNodeWrapperByTestId("2:hidden"); 3067 | const node4Wrapper = tree.getNodeWrapperByTestId("4:hidden"); 3068 | const node5Wrapper = tree.getNodeWrapperByTestId("5:hidden"); 3069 | 3070 | await expect(node1Wrapper).toBeVisible(); 3071 | await expect(node2Wrapper).toBeVisible(); 3072 | await expect(node4Wrapper).toBeVisible(); 3073 | await expect(node5Wrapper).toBeVisible(); 3074 | 3075 | // Now collapse node 2 to hide its children 3076 | await collapseNode2Button.click(); 3077 | await expect 3078 | .poll(async () => { 3079 | const state = await testStateDriver.testState(); 3080 | return state.actionPerformed; 3081 | }) 3082 | .toBe("collapseNode2"); 3083 | 3084 | // After collapse, node 2 is still visible but its children (4, 5) are hidden 3085 | await expect(node1Wrapper).toBeVisible(); 3086 | await expect(node2Wrapper).toBeVisible(); 3087 | await expect(node4Wrapper).not.toBeVisible(); 3088 | await expect(node5Wrapper).not.toBeVisible(); 3089 | 3090 | // TEST: getNodeById(1) should work for visible node 3091 | await getVisibleNode1Button.click(); 3092 | await expect 3093 | .poll(async () => { 3094 | const state = await testStateDriver.testState(); 3095 | return state.actionPerformed; 3096 | }) 3097 | .toBe("getVisibleNode1"); 3098 | 3099 | let currentState = await testStateDriver.testState(); 3100 | expect(currentState.nodeKey).toBe(1); 3101 | expect(currentState.nodeName).toBe("Root Level 0"); 3102 | expect(currentState.nodeExists).toBe(true); 3103 | 3104 | // TEST: getNodeById(4) should work even though node 4 is hidden (collapsed parent) 3105 | // This tests whether the API can access data model nodes regardless of DOM visibility 3106 | await getHiddenNode4Button.click(); 3107 | await expect 3108 | .poll(async () => { 3109 | const state = await testStateDriver.testState(); 3110 | return state.actionPerformed; 3111 | }) 3112 | .toBe("getHiddenNode4"); 3113 | 3114 | currentState = await testStateDriver.testState(); 3115 | expect(currentState.nodeKey).toBe(4); 3116 | expect(currentState.nodeName).toBe("Level 2 - Hidden Leaf A1"); 3117 | expect(currentState.nodeExists).toBe(true); 3118 | 3119 | // TEST: getNodeById(5) should also work for another hidden node 3120 | await getHiddenNode5Button.click(); 3121 | await expect 3122 | .poll(async () => { 3123 | const state = await testStateDriver.testState(); 3124 | return state.actionPerformed; 3125 | }) 3126 | .toBe("getHiddenNode5"); 3127 | 3128 | currentState = await testStateDriver.testState(); 3129 | expect(currentState.nodeKey).toBe(5); 3130 | expect(currentState.nodeName).toBe("Level 2 - Hidden Leaf A2"); 3131 | expect(currentState.nodeExists).toBe(true); 3132 | 3133 | // Now expand the parent node to make children visible again 3134 | await expandNode2Button.click(); 3135 | await expect 3136 | .poll(async () => { 3137 | const state = await testStateDriver.testState(); 3138 | return state.actionPerformed; 3139 | }) 3140 | .toBe("expandNode2"); 3141 | 3142 | // Verify nodes are now visible in DOM again 3143 | await expect(node4Wrapper).toBeVisible(); 3144 | await expect(node5Wrapper).toBeVisible(); 3145 | 3146 | // TEST: getNodeById(4) should still work now that it's visible again 3147 | await getNowVisibleNode4Button.click(); 3148 | await expect 3149 | .poll(async () => { 3150 | const state = await testStateDriver.testState(); 3151 | return state.actionPerformed; 3152 | }) 3153 | .toBe("getNowVisibleNode4"); 3154 | 3155 | currentState = await testStateDriver.testState(); 3156 | expect(currentState.nodeKey).toBe(4); 3157 | expect(currentState.nodeName).toBe("Level 2 - Hidden Leaf A1"); 3158 | expect(currentState.nodeExists).toBe(true); 3159 | }); 3160 | 3161 | test("getExpandedNodes() - returns array of expanded node keys", async ({ 3162 | initTestBed, 3163 | createTreeDriver, 3164 | createButtonDriver, 3165 | }) => { 3166 | // Hierarchy data for testing expanded nodes tracking 3167 | const hierarchyData = [ 3168 | { 3169 | id: 1, 3170 | name: "Root Level 0", 3171 | children: [ 3172 | { 3173 | id: 2, 3174 | name: "Level 1 - Branch A", 3175 | children: [ 3176 | { id: 4, name: "Level 2 - Leaf A1", children: [] }, 3177 | { id: 5, name: "Level 2 - Leaf A2", children: [] }, 3178 | ], 3179 | }, 3180 | { id: 3, name: "Level 1 - Branch B", children: [] }, 3181 | ], 3182 | }, 3183 | { 3184 | id: 6, 3185 | name: "Root Level 1", 3186 | children: [{ id: 7, name: "Level 1 - Branch C", children: [] }], 3187 | }, 3188 | ]; 3189 | 3190 | const { testStateDriver } = await initTestBed(` 3191 | <Fragment> 3192 | <VStack height="400px"> 3193 | <Tree id="treeApi" testId="tree" 3194 | dataFormat="hierarchy" 3195 | defaultExpanded="none" 3196 | data='{${JSON.stringify(hierarchyData)}}'> 3197 | <property name="itemTemplate"> 3198 | <HStack testId="{$item.id}:expand"> 3199 | <Text value="{$item.name}" /> 3200 | </HStack> 3201 | </property> 3202 | </Tree> 3203 | </VStack> 3204 | <Button testId="get-expanded-initial-btn" label="Get Expanded Initial" onClick=" 3205 | const expanded = treeApi.getExpandedNodes(); 3206 | testState = { 3207 | actionPerformed: 'getExpandedInitial', 3208 | expandedNodes: expanded, 3209 | expandedCount: expanded.length 3210 | }; 3211 | " /> 3212 | <Button testId="expand-node1-btn" label="Expand Node 1" onClick=" 3213 | treeApi.expandNode(1); 3214 | testState = { actionPerformed: 'expandNode1' }; 3215 | " /> 3216 | <Button testId="get-expanded-after1-btn" label="Get Expanded After 1" onClick=" 3217 | const expanded = treeApi.getExpandedNodes(); 3218 | testState = { 3219 | actionPerformed: 'getExpandedAfter1', 3220 | expandedNodes: expanded, 3221 | expandedCount: expanded.length 3222 | }; 3223 | " /> 3224 | <Button testId="expand-node2-btn" label="Expand Node 2" onClick=" 3225 | treeApi.expandNode(2); 3226 | testState = { actionPerformed: 'expandNode2' }; 3227 | " /> 3228 | <Button testId="get-expanded-after2-btn" label="Get Expanded After 2" onClick=" 3229 | const expanded = treeApi.getExpandedNodes(); 3230 | testState = { 3231 | actionPerformed: 'getExpandedAfter2', 3232 | expandedNodes: expanded, 3233 | expandedCount: expanded.length 3234 | }; 3235 | " /> 3236 | <Button testId="expand-all-btn" label="Expand All" onClick=" 3237 | treeApi.expandAll(); 3238 | testState = { actionPerformed: 'expandAll' }; 3239 | " /> 3240 | <Button testId="get-expanded-all-btn" label="Get Expanded All" onClick=" 3241 | const expanded = treeApi.getExpandedNodes(); 3242 | testState = { 3243 | actionPerformed: 'getExpandedAll', 3244 | expandedNodes: expanded, 3245 | expandedCount: expanded.length 3246 | }; 3247 | " /> 3248 | <Button testId="collapse-node2-btn" label="Collapse Node 2" onClick=" 3249 | treeApi.collapseNode(2); 3250 | testState = { actionPerformed: 'collapseNode2' }; 3251 | " /> 3252 | <Button testId="get-expanded-after-collapse-btn" label="Get Expanded After Collapse" onClick=" 3253 | const expanded = treeApi.getExpandedNodes(); 3254 | testState = { 3255 | actionPerformed: 'getExpandedAfterCollapse', 3256 | expandedNodes: expanded, 3257 | expandedCount: expanded.length 3258 | }; 3259 | " /> 3260 | </Fragment> 3261 | `); 3262 | 3263 | const getExpandedInitialButton = await createButtonDriver("get-expanded-initial-btn"); 3264 | const expandNode1Button = await createButtonDriver("expand-node1-btn"); 3265 | const getExpandedAfter1Button = await createButtonDriver("get-expanded-after1-btn"); 3266 | const expandNode2Button = await createButtonDriver("expand-node2-btn"); 3267 | const getExpandedAfter2Button = await createButtonDriver("get-expanded-after2-btn"); 3268 | const expandAllButton = await createButtonDriver("expand-all-btn"); 3269 | const getExpandedAllButton = await createButtonDriver("get-expanded-all-btn"); 3270 | const collapseNode2Button = await createButtonDriver("collapse-node2-btn"); 3271 | const getExpandedAfterCollapseButton = await createButtonDriver( 3272 | "get-expanded-after-collapse-btn", 3273 | ); 3274 | 3275 | // INITIAL STATE: defaultExpanded="none", so no nodes should be expanded 3276 | await getExpandedInitialButton.click(); 3277 | await expect 3278 | .poll(async () => { 3279 | const state = await testStateDriver.testState(); 3280 | return state.actionPerformed; 3281 | }) 3282 | .toBe("getExpandedInitial"); 3283 | 3284 | let currentState = await testStateDriver.testState(); 3285 | expect(currentState.expandedCount).toBe(0); 3286 | expect(currentState.expandedNodes).toEqual([]); 3287 | 3288 | // EXPAND NODE 1: Should add node 1 to expanded list 3289 | await expandNode1Button.click(); 3290 | await expect 3291 | .poll(async () => { 3292 | const state = await testStateDriver.testState(); 3293 | return state.actionPerformed; 3294 | }) 3295 | .toBe("expandNode1"); 3296 | 3297 | await getExpandedAfter1Button.click(); 3298 | await expect 3299 | .poll(async () => { 3300 | const state = await testStateDriver.testState(); 3301 | return state.actionPerformed; 3302 | }) 3303 | .toBe("getExpandedAfter1"); 3304 | 3305 | currentState = await testStateDriver.testState(); 3306 | expect(currentState.expandedCount).toBe(1); 3307 | expect(currentState.expandedNodes).toEqual([1]); 3308 | 3309 | // EXPAND NODE 2: Should add node 2 to expanded list (alongside node 1) 3310 | await expandNode2Button.click(); 3311 | await expect 3312 | .poll(async () => { 3313 | const state = await testStateDriver.testState(); 3314 | return state.actionPerformed; 3315 | }) 3316 | .toBe("expandNode2"); 3317 | 3318 | await getExpandedAfter2Button.click(); 3319 | await expect 3320 | .poll(async () => { 3321 | const state = await testStateDriver.testState(); 3322 | return state.actionPerformed; 3323 | }) 3324 | .toBe("getExpandedAfter2"); 3325 | 3326 | currentState = await testStateDriver.testState(); 3327 | expect(currentState.expandedCount).toBe(2); 3328 | expect(currentState.expandedNodes).toEqual(expect.arrayContaining([1, 2])); 3329 | 3330 | // EXPAND ALL: Should expand all nodes with children 3331 | await expandAllButton.click(); 3332 | await expect 3333 | .poll(async () => { 3334 | const state = await testStateDriver.testState(); 3335 | return state.actionPerformed; 3336 | }) 3337 | .toBe("expandAll"); 3338 | 3339 | await getExpandedAllButton.click(); 3340 | await expect 3341 | .poll(async () => { 3342 | const state = await testStateDriver.testState(); 3343 | return state.actionPerformed; 3344 | }) 3345 | .toBe("getExpandedAll"); 3346 | 3347 | currentState = await testStateDriver.testState(); 3348 | // After expandAll, ALL nodes are added to the expanded list 3349 | // This includes leaf nodes, which is the current implementation behavior 3350 | expect(currentState.expandedCount).toBe(7); 3351 | expect(currentState.expandedNodes).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6, 7])); 3352 | 3353 | // COLLAPSE NODE 2: Should remove node 2 from expanded list 3354 | await collapseNode2Button.click(); 3355 | await expect 3356 | .poll(async () => { 3357 | const state = await testStateDriver.testState(); 3358 | return state.actionPerformed; 3359 | }) 3360 | .toBe("collapseNode2"); 3361 | 3362 | await getExpandedAfterCollapseButton.click(); 3363 | await expect 3364 | .poll(async () => { 3365 | const state = await testStateDriver.testState(); 3366 | return state.actionPerformed; 3367 | }) 3368 | .toBe("getExpandedAfterCollapse"); 3369 | 3370 | currentState = await testStateDriver.testState(); 3371 | expect(currentState.expandedCount).toBe(4); 3372 | // When node 2 is collapsed, its descendants (4, 5) are also removed from expanded list 3373 | expect(currentState.expandedNodes).toEqual(expect.arrayContaining([1, 3, 6, 7])); 3374 | expect(currentState.expandedNodes).not.toEqual(expect.arrayContaining([2, 4, 5])); 3375 | }); 3376 | }); 3377 | }); 3378 | 3379 | // ============================================================================= 3380 | // DYNAMIC FIELD SUPPORT TESTS 3381 | // ============================================================================= 3382 | test.describe("Dynamic Field Support", () => { 3383 | test("should display expand/collapse icons for dynamic nodes even without children", async ({ 3384 | page, 3385 | initTestBed, 3386 | createTreeDriver, 3387 | }) => { 3388 | await initTestBed(` 3389 | <VStack height="400px"> 3390 | <Tree testId="tree" 3391 | dataFormat="hierarchy" 3392 | data='{${JSON.stringify(dynamicTreeData)}}' 3393 | defaultExpanded="none"> 3394 | <property name="itemTemplate"> 3395 | <HStack testId="{$item.id}:{$item.depth}"> 3396 | <Text value="{$item.name}" /> 3397 | </HStack> 3398 | </property> 3399 | </Tree> 3400 | </VStack> 3401 | `); 3402 | 3403 | const tree = await createTreeDriver("tree"); 3404 | await expect(tree.component).toBeVisible(); 3405 | 3406 | // --- We see an extra icon because Node 3 is dynamic --- 3407 | await expect(tree.getIconsByName("chevronright")).toHaveCount(2); 3408 | 3409 | // Verify tree items are rendered (only root level nodes should be visible initially) 3410 | await expect(page.getByTestId("1:0")).toBeVisible(); 3411 | await expect(page.getByTestId("3:0")).toBeVisible(); 3412 | await expect(page.getByTestId("4:0")).toBeVisible(); 3413 | 3414 | // Node 2 should not be visible initially as parent is collapsed 3415 | await expect(page.getByTestId("2:1")).not.toBeVisible(); 3416 | }); 3417 | 3418 | test("should use custom dynamicField name", async ({ page, initTestBed, createTreeDriver }) => { 3419 | await initTestBed(` 3420 | <VStack height="400px"> 3421 | <Tree testId="tree" 3422 | dataFormat="hierarchy" 3423 | dynamicField="canLoadMore" 3424 | data='{${JSON.stringify(customDynamicTreeData)}}' 3425 | defaultExpanded="none"> 3426 | <property name="itemTemplate"> 3427 | <HStack testId="{$item.id}:{$item.depth}"> 3428 | <Text value="{$item.name}" /> 3429 | </HStack> 3430 | </property> 3431 | </Tree> 3432 | </VStack> 3433 | `); 3434 | 3435 | const tree = await createTreeDriver("tree"); 3436 | await expect(tree.component).toBeVisible(); 3437 | 3438 | // --- We see an extra icon because Node 3 is dynamic --- 3439 | await expect(tree.getIconsByName("chevronright")).toHaveCount(2); 3440 | 3441 | // Verify tree items are rendered (only root level nodes should be visible initially) 3442 | await expect(page.getByTestId("1:0")).toBeVisible(); 3443 | await expect(page.getByTestId("3:0")).toBeVisible(); 3444 | await expect(page.getByTestId("4:0")).toBeVisible(); 3445 | 3446 | // Node 2 should not be visible initially as parent is collapsed 3447 | await expect(page.getByTestId("2:1")).not.toBeVisible(); 3448 | }); 3449 | 3450 | test("should work with flat data format and dynamic field", async ({ 3451 | page, 3452 | initTestBed, 3453 | createTreeDriver, 3454 | }) => { 3455 | await initTestBed(` 3456 | <VStack height="400px"> 3457 | <Tree testId="tree" 3458 | dataFormat="flat" 3459 | data='{${JSON.stringify(dynamicFlatData)}}' 3460 | defaultExpanded="none"> 3461 | <property name="itemTemplate"> 3462 | <HStack testId="{$item.id}:{$item.depth}"> 3463 | <Text value="{$item.name}" /> 3464 | </HStack> 3465 | </property> 3466 | </Tree> 3467 | </VStack> 3468 | `); 3469 | 3470 | const tree = await createTreeDriver("tree"); 3471 | await expect(tree.component).toBeVisible(); 3472 | 3473 | // --- We see an extra icon because Node 3 is dynamic --- 3474 | await expect(tree.getIconsByName("chevronright")).toHaveCount(2); 3475 | 3476 | // Verify tree items are rendered (only root level nodes should be visible initially) 3477 | await expect(page.getByTestId("1:0")).toBeVisible(); 3478 | await expect(page.getByTestId("3:0")).toBeVisible(); 3479 | await expect(page.getByTestId("4:0")).toBeVisible(); 3480 | 3481 | // Node 2 should not be visible initially as parent is collapsed 3482 | await expect(page.getByTestId("2:1")).not.toBeVisible(); 3483 | }); 3484 | }); 3485 | ```