This is page 109 of 189. Use http://codebase.md/xmlui-org/xmlui/tools/vscode/resources/assets/img/%7Bsrc%7D?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ ├── cyan-tools-design.md
│ ├── every-moments-teach.md
│ ├── fancy-laws-drop.md
│ ├── full-symbols-accept.md
│ └── tricky-zoos-crash.md
├── .eslintrc.cjs
├── .github
│ ├── build-checklist.png
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows
│ ├── deploy-blog-optimized.yml
│ ├── deploy-blog-swa.yml
│ ├── deploy-blog.yml
│ ├── deploy-docs-optimized.yml
│ ├── deploy-docs-swa.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
│ │ │ │ ├── an-advanced-codefence.gif
│ │ │ │ ├── an-advanced-codefence.mp4
│ │ │ │ ├── blog-page-component.png
│ │ │ │ ├── blog-scrabble.png
│ │ │ │ ├── codefence-runner.png
│ │ │ │ ├── integrated-blog-search.png
│ │ │ │ ├── lorem-ipsum.png
│ │ │ │ ├── playground-checkbox-source.png
│ │ │ │ ├── playground.png
│ │ │ │ ├── use-xmlui-mcp-to-find-a-howto.png
│ │ │ │ └── xmlui-demo-gallery.png
│ │ │ ├── introducing-xmlui.md
│ │ │ ├── lorem-ipsum.md
│ │ │ ├── newest-post.md
│ │ │ ├── older-post.md
│ │ │ ├── xmlui-playground.md
│ │ │ └── xmlui-powered-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
│ │ │ ├── rss.svg
│ │ │ └── xmlui-logo.svg
│ │ ├── serve.json
│ │ ├── staticwebapp.config.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
│ │ └── blog-theme.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
│ │ │ │ ├── control-cache-invalidation.md
│ │ │ │ ├── debounce-user-input-for-api-calls.md
│ │ │ │ ├── debounce-with-changelistener.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
│ │ │ │ ├── make-navpanel-width-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
│ ├── tsconfig.json
│ ├── 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
│ ├── 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
│ │ └── 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
│ ├── 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
│ ├── 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
│ ├── 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.module.scss
│ │ │ ├── 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.tsx
│ │ │ ├── StandalonePlayground.tsx
│ │ │ ├── StandalonePlaygroundNative.module.scss
│ │ │ ├── StandalonePlaygroundNative.tsx
│ │ │ ├── ThemeSwitcher.module.scss
│ │ │ ├── ThemeSwitcher.tsx
│ │ │ └── utils.ts
│ │ ├── providers
│ │ │ ├── Toast.module.scss
│ │ │ └── ToastProvider.tsx
│ │ ├── state
│ │ │ └── store.ts
│ │ ├── themes
│ │ │ └── theme.ts
│ │ └── utils
│ │ └── helpers.ts
│ ├── 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
│ ├── xmlui-spreadsheet
│ │ ├── .gitignore
│ │ ├── demo
│ │ │ └── Main.xmlui
│ │ ├── index.html
│ │ ├── index.ts
│ │ ├── meta
│ │ │ └── componentsMetadata.ts
│ │ ├── package.json
│ │ └── src
│ │ ├── index.tsx
│ │ ├── Spreadsheet.tsx
│ │ └── SpreadsheetNative.tsx
│ └── 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.spec.ts
│ │ ├── HeroSection.tsx
│ │ └── HeroSectionNative.tsx
│ ├── index.tsx
│ ├── ScrollToTop
│ │ ├── ScrollToTop.module.scss
│ │ ├── ScrollToTop.tsx
│ │ └── ScrollToTopNative.tsx
│ └── vite-env.d.ts
├── playwright.config.ts
├── 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.cjs
│ ├── 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
│ ├── build-system.md
│ ├── build-xmlui.md
│ ├── component-behaviors.md
│ ├── component-metadata.md
│ ├── components-with-options.md
│ ├── containers.md
│ ├── data-operations.md
│ ├── glossary.md
│ ├── index.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
│ ├── standalone-app.md
│ ├── theme-variables-refactoring.md
│ ├── ud-components.md
│ └── xmlui-repo.md
├── package.json
├── scripts
│ ├── coverage-only.js
│ ├── e2e-test-summary.js
│ ├── extract-component-metadata.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
│ ├── generate-metadata-markdown.js
│ ├── get-langserver-metadata.js
│ ├── 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.module.scss
│ │ │ │ ├── LabelList.spec.ts
│ │ │ │ ├── LabelList.tsx
│ │ │ │ └── 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
│ │ │ └── test-padding.xmlui
│ │ ├── 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
│ │ │ ├── OptionContext.ts
│ │ │ ├── Select.md
│ │ │ ├── Select.module.scss
│ │ │ ├── Select.spec.ts
│ │ │ ├── Select.tsx
│ │ │ ├── SelectContext.tsx
│ │ │ └── SelectNative.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
│ │ │ └── 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
│ │ │ ├── base64-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.js
│ ├── 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
│ │ │ ├── index.ts
│ │ │ ├── ModalDialogDriver.ts
│ │ │ ├── NumberBoxDriver.ts
│ │ │ ├── TextBoxDriver.ts
│ │ │ ├── TimeInputDriver.ts
│ │ │ ├── TimerDriver.ts
│ │ │ └── TreeDriver.ts
│ │ ├── fixtures.ts
│ │ ├── index.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.json
├── tsdown.config.ts
├── vite.config.ts
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/xmlui/src/components/Queue/Queue.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { expect, test } from "../../testing/fixtures";
2 |
3 | // =============================================================================
4 | // BASIC FUNCTIONALITY TESTS
5 | // =============================================================================
6 |
7 | test.describe("Basic Functionality", () => {
8 | test("component initializes and provides API methods", async ({
9 | initTestBed,
10 | createButtonDriver,
11 | }) => {
12 | const { testStateDriver } = await initTestBed(`
13 | <Fragment>
14 | <Queue id="testQueue" />
15 | <Button id="checkApi" label="Check API" onClick="testState = {
16 | hasEnqueueItem: typeof testQueue.enqueueItem === 'function',
17 | hasEnqueueItems: typeof testQueue.enqueueItems === 'function',
18 | hasGetQueuedItems: typeof testQueue.getQueuedItems === 'function',
19 | hasGetQueueLength: typeof testQueue.getQueueLength === 'function',
20 | hasRemove: typeof testQueue.remove === 'function'
21 | }" />
22 | </Fragment>
23 | `);
24 |
25 | const buttonDriver = await createButtonDriver("checkApi");
26 | await buttonDriver.component.click();
27 | await expect.poll(testStateDriver.testState).toEqual({
28 | hasEnqueueItem: true,
29 | hasEnqueueItems: true,
30 | hasGetQueuedItems: true,
31 | hasGetQueueLength: true,
32 | hasRemove: true,
33 | });
34 | });
35 |
36 | // =============================================================================
37 | // ENQUEUE ITEM API TESTS
38 | // =============================================================================
39 |
40 | test.describe("enqueueItem API", () => {
41 | test("enqueueItem adds item to queue and returns ID", async ({
42 | initTestBed,
43 | createButtonDriver,
44 | }) => {
45 | const { testStateDriver } = await initTestBed(`
46 | <Fragment>
47 | <Queue id="testQueue" />
48 | <Button id="enqueueBtn" label="Enqueue" onClick="
49 | const itemId = testQueue.enqueueItem('test-item');
50 | testState = {
51 | itemId: itemId,
52 | hasValidId: typeof itemId === 'string' && itemId.length > 0
53 | };
54 | " />
55 | </Fragment>
56 | `);
57 |
58 | const buttonDriver = await createButtonDriver("enqueueBtn");
59 | await buttonDriver.component.click();
60 |
61 | await expect.poll(testStateDriver.testState).toEqual({
62 | itemId: expect.any(String),
63 | hasValidId: true,
64 | });
65 | });
66 |
67 | test("enqueueItem handles different data types", async ({
68 | initTestBed,
69 | createButtonDriver,
70 | }) => {
71 | const { testStateDriver } = await initTestBed(`
72 | <Fragment>
73 | <Queue id="testQueue" />
74 | <Button id="enqueueBtn" label="Enqueue" onClick="
75 | const results = [];
76 | results.push(testQueue.enqueueItem('string'));
77 | results.push(testQueue.enqueueItem(123));
78 | results.push(testQueue.enqueueItem({key: 'value'}));
79 | results.push(testQueue.enqueueItem([1, 2, 3]));
80 | results.push(testQueue.enqueueItem(null));
81 | results.push(testQueue.enqueueItem(undefined));
82 | testState = results.length;
83 | " />
84 | </Fragment>
85 | `);
86 |
87 | const buttonDriver = await createButtonDriver("enqueueBtn");
88 | await buttonDriver.component.click();
89 |
90 | await expect.poll(testStateDriver.testState).toBe(6);
91 | });
92 |
93 | test("enqueueItem generates unique IDs", async ({ initTestBed, createButtonDriver }) => {
94 | const { testStateDriver } = await initTestBed(`
95 | <Fragment>
96 | <Queue id="testQueue" />
97 | <Button id="enqueueBtn" label="Enqueue" onClick="
98 | const id1 = testQueue.enqueueItem('item1');
99 | const id2 = testQueue.enqueueItem('item2');
100 | const id3 = testQueue.enqueueItem('item3');
101 | testState = {
102 | allDifferent: id1 !== id2 && id2 !== id3 && id1 !== id3,
103 | ids: [id1, id2, id3]
104 | };
105 | " />
106 | </Fragment>
107 | `);
108 |
109 | const buttonDriver = await createButtonDriver("enqueueBtn");
110 | await buttonDriver.component.click();
111 |
112 | const result = await testStateDriver.testState();
113 | expect(result.allDifferent).toBe(true);
114 | expect(result.ids).toHaveLength(3);
115 | });
116 | });
117 |
118 | // =============================================================================
119 | // ENQUEUE ITEMS API TESTS
120 | // =============================================================================
121 |
122 | test.describe("enqueueItems API", () => {
123 | test("enqueueItems adds multiple items and returns array of IDs", async ({
124 | initTestBed,
125 | createButtonDriver,
126 | }) => {
127 | const { testStateDriver } = await initTestBed(`
128 | <Fragment>
129 | <Queue id="testQueue" />
130 | <Button id="enqueueBtn" label="Enqueue" onClick="
131 | const itemIds = testQueue.enqueueItems(['item1', 'item2', 'item3']);
132 | testState = {
133 | itemIds: itemIds,
134 | isArray: Array.isArray(itemIds),
135 | correctLength: itemIds.length === 3
136 | };
137 | " />
138 | </Fragment>
139 | `);
140 |
141 | const buttonDriver = await createButtonDriver("enqueueBtn");
142 | await buttonDriver.component.click();
143 |
144 | await expect.poll(testStateDriver.testState).toEqual({
145 | itemIds: expect.any(Array),
146 | isArray: true,
147 | correctLength: true,
148 | });
149 | });
150 |
151 | test("enqueueItems handles empty array", async ({ initTestBed, createButtonDriver }) => {
152 | const { testStateDriver } = await initTestBed(`
153 | <Fragment>
154 | <Queue id="testQueue" />
155 | <Button id="enqueueBtn" label="Enqueue" onClick="
156 | const itemIds = testQueue.enqueueItems([]);
157 | testState = {
158 | itemIds: itemIds,
159 | queueLength: testQueue.getQueueLength(),
160 | isEmptyArray: Array.isArray(itemIds) && itemIds.length === 0
161 | };
162 | " />
163 | </Fragment>
164 | `);
165 |
166 | const buttonDriver = await createButtonDriver("enqueueBtn");
167 | await buttonDriver.component.click();
168 |
169 | await expect.poll(testStateDriver.testState).toEqual({
170 | itemIds: [],
171 | queueLength: 0,
172 | isEmptyArray: true,
173 | });
174 | });
175 | });
176 |
177 | // =============================================================================
178 | // GET QUEUE LENGTH API TESTS
179 | // =============================================================================
180 |
181 | test.describe("getQueueLength API", () => {
182 | test("getQueueLength returns 0 for empty queue", async ({
183 | initTestBed,
184 | createButtonDriver,
185 | }) => {
186 | const { testStateDriver } = await initTestBed(`
187 | <Fragment>
188 | <Queue id="testQueue" />
189 | <Button id="checkBtn" label="Check" onClick="testState = testQueue.getQueueLength();" />
190 | </Fragment>
191 | `);
192 |
193 | const buttonDriver = await createButtonDriver("checkBtn");
194 | await buttonDriver.component.click();
195 |
196 | await expect.poll(testStateDriver.testState).toBe(0);
197 | });
198 |
199 | test("getQueueLength updates after adding items", async ({
200 | initTestBed,
201 | createButtonDriver,
202 | }) => {
203 | const { testStateDriver } = await initTestBed(`
204 | <Fragment>
205 | <Queue id="testQueue" />
206 | <Button id="testBtn" label="Test" onClick="
207 | const initial = testQueue.getQueueLength();
208 | const id1 = testQueue.enqueueItem('item1');
209 | const afterOne = testQueue.getQueueLength();
210 | const ids = testQueue.enqueueItems(['item2', 'item3']);
211 | const afterThree = testQueue.getQueueLength();
212 | testState = {
213 | initial,
214 | afterOne,
215 | afterThree,
216 | hasId1: !!id1,
217 | hasIds: ids.length === 2
218 | };
219 | " />
220 | </Fragment>
221 | `);
222 |
223 | const buttonDriver = await createButtonDriver("testBtn");
224 | await buttonDriver.component.click();
225 |
226 | await expect.poll(testStateDriver.testState).toEqual({
227 | initial: 0,
228 | afterOne: 0,
229 | afterThree: 0,
230 | hasId1: true,
231 | hasIds: true,
232 | });
233 | });
234 | });
235 |
236 | // =============================================================================
237 | // GET QUEUED ITEMS API TESTS
238 | // =============================================================================
239 |
240 | test.describe("getQueuedItems API", () => {
241 | test("getQueuedItems returns empty array for empty queue", async ({
242 | initTestBed,
243 | createButtonDriver,
244 | }) => {
245 | const { testStateDriver } = await initTestBed(`
246 | <Fragment>
247 | <Queue id="testQueue" />
248 | <Button id="checkBtn" label="Check" onClick="testState = testQueue.getQueuedItems();" />
249 | </Fragment>
250 | `);
251 |
252 | const buttonDriver = await createButtonDriver("checkBtn");
253 | await buttonDriver.component.click();
254 |
255 | await expect.poll(testStateDriver.testState).toEqual([]);
256 | });
257 |
258 | test("getQueuedItems returns items with correct structure", async ({
259 | initTestBed,
260 | createButtonDriver,
261 | }) => {
262 | const { testStateDriver } = await initTestBed(`
263 | <Fragment>
264 | <Queue id="testQueue" />
265 | <Button id="testBtn" label="Test" onClick="
266 | const itemId = testQueue.enqueueItem('test-item');
267 | const items = testQueue.getQueuedItems();
268 | testState = {
269 | hasItemId: !!itemId,
270 | itemsLength: items.length,
271 | isArray: Array.isArray(items)
272 | };
273 | " />
274 | </Fragment>
275 | `);
276 |
277 | const buttonDriver = await createButtonDriver("testBtn");
278 | await buttonDriver.component.click();
279 |
280 | await expect.poll(testStateDriver.testState).toEqual({
281 | hasItemId: true,
282 | itemsLength: 0,
283 | isArray: true,
284 | });
285 | });
286 | });
287 |
288 | // =============================================================================
289 | // REMOVE API TESTS
290 | // =============================================================================
291 |
292 | test.describe("remove API", () => {
293 | test("remove removes item from queue by ID", async ({ initTestBed, createButtonDriver }) => {
294 | const { testStateDriver } = await initTestBed(`
295 | <Fragment>
296 | <Queue id="testQueue" />
297 | <Button id="testBtn" label="Test" onClick="
298 | const itemId = testQueue.enqueueItem('item-to-remove');
299 | const keepId = testQueue.enqueueItem('item-to-keep');
300 | const lengthBefore = testQueue.getQueueLength();
301 | testQueue.remove(itemId);
302 | const lengthAfter = testQueue.getQueueLength();
303 | testState = {
304 | lengthBefore,
305 | lengthAfter,
306 | hasItemId: !!itemId,
307 | hasKeepId: !!keepId
308 | };
309 | " />
310 | </Fragment>
311 | `);
312 |
313 | const buttonDriver = await createButtonDriver("testBtn");
314 | await buttonDriver.component.click();
315 |
316 | await expect.poll(testStateDriver.testState).toEqual({
317 | lengthBefore: 0,
318 | lengthAfter: 0,
319 | hasItemId: true,
320 | hasKeepId: true,
321 | });
322 | });
323 |
324 | test("remove handles invalid ID gracefully", async ({ initTestBed, createButtonDriver }) => {
325 | const { testStateDriver } = await initTestBed(`
326 | <Fragment>
327 | <Queue id="testQueue" />
328 | <Button id="testBtn" label="Test" onClick="
329 | const itemId = testQueue.enqueueItem('test-item');
330 | const lengthBefore = testQueue.getQueueLength();
331 | testQueue.remove('invalid-id');
332 | const lengthAfter = testQueue.getQueueLength();
333 | testState = {
334 | lengthBefore,
335 | lengthAfter,
336 | hasItemId: !!itemId
337 | };
338 | " />
339 | </Fragment>
340 | `);
341 |
342 | const buttonDriver = await createButtonDriver("testBtn");
343 | await buttonDriver.component.click();
344 |
345 | await expect.poll(testStateDriver.testState).toEqual({
346 | lengthBefore: 0,
347 | lengthAfter: 0,
348 | hasItemId: true,
349 | });
350 | });
351 | });
352 |
353 | // =============================================================================
354 | // CLEAR AFTER FINISH PROPERTY TESTS
355 | // =============================================================================
356 |
357 | test.describe("clearAfterFinish property", () => {
358 | test("clearAfterFinish defaults to false", async ({ initTestBed, createButtonDriver }) => {
359 | const { testStateDriver } = await initTestBed(`
360 | <Fragment>
361 | <Queue id="testQueue" onProcess="processing => testState = 'processed'" />
362 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
363 | </Fragment>
364 | `);
365 |
366 | const buttonDriver = await createButtonDriver("enqueueBtn");
367 | await buttonDriver.component.click();
368 |
369 | await expect.poll(testStateDriver.testState).toBe("processed");
370 | });
371 |
372 | test("clearAfterFinish=true removes completed items", async ({
373 | initTestBed,
374 | createButtonDriver,
375 | }) => {
376 | const { testStateDriver } = await initTestBed(`
377 | <Fragment>
378 | <Queue id="testQueue" clearAfterFinish="true"
379 | onProcess="processing => {}"
380 | onComplete="() => testState = testQueue.getQueuedItems().length" />
381 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
382 | </Fragment>
383 | `);
384 |
385 | const buttonDriver = await createButtonDriver("enqueueBtn");
386 | await buttonDriver.component.click();
387 |
388 | await expect.poll(testStateDriver.testState).toBe(0);
389 | });
390 | });
391 |
392 | // =============================================================================
393 | // EVENT HANDLER TESTS
394 | // =============================================================================
395 |
396 | test.describe("Event Handlers", () => {
397 | test("onProcess event fires for queued items", async ({ initTestBed, createButtonDriver }) => {
398 | const { testStateDriver } = await initTestBed(`
399 | <Fragment>
400 | <Queue id="testQueue" onProcess="processing => testState = processing.item" />
401 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test-data');" />
402 | </Fragment>
403 | `);
404 |
405 | const buttonDriver = await createButtonDriver("enqueueBtn");
406 | await buttonDriver.component.click();
407 |
408 | await expect.poll(testStateDriver.testState).toBe("test-data");
409 | });
410 |
411 | test("onWillProcess event can skip items by returning false", async ({
412 | initTestBed,
413 | createButtonDriver,
414 | }) => {
415 | const { testStateDriver } = await initTestBed(`
416 | <Fragment>
417 | <Queue id="testQueue"
418 | onWillProcess="processing => processing.item !== 'skip' ? true : (testState = 'skipped', false)"
419 | onProcess="processing => testState = 'processed'" />
420 | <Button id="enqueueBtn" label="Enqueue" onClick="
421 | testQueue.enqueueItem('skip');
422 | testQueue.enqueueItem('process');
423 | " />
424 | </Fragment>
425 | `);
426 |
427 | const buttonDriver = await createButtonDriver("enqueueBtn");
428 | await buttonDriver.component.click();
429 |
430 | await expect.poll(testStateDriver.testState).toBe("processed");
431 | });
432 |
433 | test("onDidProcess event fires after processing", async ({
434 | initTestBed,
435 | createButtonDriver,
436 | }) => {
437 | const { testStateDriver } = await initTestBed(`
438 | <Fragment>
439 | <Queue id="testQueue"
440 | onProcess="processing => {}"
441 | onDidProcess="processing => testState = 'did-process-' + processing.item" />
442 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
443 | </Fragment>
444 | `);
445 |
446 | const buttonDriver = await createButtonDriver("enqueueBtn");
447 | await buttonDriver.component.click();
448 |
449 | await expect.poll(testStateDriver.testState).toBe("did-process-test");
450 | });
451 |
452 | test("onProcessError event fires when processing throws", async ({
453 | initTestBed,
454 | createButtonDriver,
455 | }) => {
456 | const { testStateDriver } = await initTestBed(`
457 | <Fragment>
458 | <Queue id="testQueue"
459 | onProcess="processing => { throw 'test error'; }"
460 | onProcessError="(error, processing) => testState = 'error-' + processing.item" />
461 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
462 | </Fragment>
463 | `);
464 |
465 | const buttonDriver = await createButtonDriver("enqueueBtn");
466 | await buttonDriver.component.click();
467 |
468 | await expect.poll(testStateDriver.testState).toBe("error-test");
469 | });
470 |
471 | test("onComplete event fires when queue becomes empty", async ({
472 | initTestBed,
473 | createButtonDriver,
474 | }) => {
475 | const { testStateDriver } = await initTestBed(`
476 | <Fragment>
477 | <Queue id="testQueue" clearAfterFinish="true"
478 | onProcess="processing => {}"
479 | onComplete="() => testState = 'complete'" />
480 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
481 | </Fragment>
482 | `);
483 |
484 | const buttonDriver = await createButtonDriver("enqueueBtn");
485 | await buttonDriver.component.click();
486 |
487 | await expect.poll(testStateDriver.testState).toBe("complete");
488 | });
489 | });
490 |
491 | // =============================================================================
492 | // TEMPLATE PROPERTY TESTS
493 | // =============================================================================
494 |
495 | test.describe("Template Properties", () => {
496 | test("progressFeedback renders during processing", async ({
497 | initTestBed,
498 | page,
499 | createButtonDriver,
500 | }) => {
501 | await initTestBed(`
502 | <Fragment>
503 | <Queue id="testQueue" onProcess="processing => { processing.reportProgress('50%'); }">
504 | <property name="progressFeedback">
505 | <Text value="Progress: {$completedItems.length} of {$queuedItems.length}" />
506 | </property>
507 | </Queue>
508 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
509 | </Fragment>
510 | `);
511 |
512 | const buttonDriver = await createButtonDriver("enqueueBtn");
513 | await buttonDriver.component.click();
514 |
515 | await expect(page.getByText("Progress: 0 of 1")).toBeVisible();
516 | });
517 |
518 | test("resultFeedback renders when queue completes", async ({
519 | initTestBed,
520 | page,
521 | createButtonDriver,
522 | }) => {
523 | await initTestBed(`
524 | <Fragment>
525 | <Queue id="testQueue" clearAfterFinish="true" onProcess="processing => {}">
526 | <property name="resultFeedback">
527 | <Text value="All {$completedItems.length} items processed" />
528 | </property>
529 | </Queue>
530 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
531 | </Fragment>
532 | `);
533 |
534 | const buttonDriver = await createButtonDriver("enqueueBtn");
535 | await buttonDriver.component.click();
536 |
537 | await expect(page.getByText("All 1 items processed")).toBeVisible();
538 | });
539 |
540 | test("progressFeedback handles null gracefully", async ({
541 | initTestBed,
542 | createButtonDriver,
543 | }) => {
544 | const { testStateDriver } = await initTestBed(`
545 | <Fragment>
546 | <Queue id="testQueue" progressFeedback="{null}" onProcess="processing => testState = 'processed'" />
547 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
548 | </Fragment>
549 | `);
550 |
551 | const buttonDriver = await createButtonDriver("enqueueBtn");
552 | await buttonDriver.component.click();
553 |
554 | await expect.poll(testStateDriver.testState).toBe("processed");
555 | });
556 |
557 | test("resultFeedback handles null gracefully", async ({ initTestBed, createButtonDriver }) => {
558 | const { testStateDriver } = await initTestBed(`
559 | <Fragment>
560 | <Queue id="testQueue" resultFeedback="{null}" clearAfterFinish="true"
561 | onProcess="processing => {}"
562 | onComplete="() => testState = 'complete'" />
563 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItem('test');" />
564 | </Fragment>
565 | `);
566 |
567 | const buttonDriver = await createButtonDriver("enqueueBtn");
568 | await buttonDriver.component.click();
569 |
570 | await expect.poll(testStateDriver.testState).toBe("complete");
571 | });
572 | });
573 | });
574 |
575 | // =============================================================================
576 | // OTHER EDGE CASE TESTS
577 | // =============================================================================
578 |
579 | test.describe("Other Edge Cases", () => {
580 | test("handles simultaneous API calls", async ({ initTestBed, createButtonDriver }) => {
581 | const { testStateDriver } = await initTestBed(`
582 | <Fragment>
583 | <Queue id="testQueue" />
584 | <Button id="callBtn" label="Call APIs" onClick="
585 | // Add multiple items quickly
586 | const id1 = testQueue.enqueueItem('item1');
587 | const id2 = testQueue.enqueueItem('item2');
588 | const ids = testQueue.enqueueItems(['item3', 'item4', 'item5']);
589 |
590 | // Mix operations
591 | testQueue.remove(id1);
592 | const finalLength = testQueue.getQueueLength();
593 | const items = testQueue.getQueuedItems();
594 |
595 | testState = {
596 | finalLength,
597 | itemCount: items.length,
598 | hasId1: !!id1,
599 | hasId2: !!id2,
600 | idsLength: ids.length
601 | };
602 | " />
603 | </Fragment>
604 | `);
605 |
606 | const buttonDriver = await createButtonDriver("callBtn");
607 | await buttonDriver.component.click();
608 |
609 | await expect.poll(testStateDriver.testState).toEqual({
610 | finalLength: 0,
611 | itemCount: 0,
612 | hasId1: true,
613 | hasId2: true,
614 | idsLength: 3,
615 | });
616 | });
617 |
618 | test("handles processing with errors and recovery", async ({
619 | initTestBed,
620 | createButtonDriver,
621 | }) => {
622 | const { testStateDriver } = await initTestBed(`
623 | <Fragment>
624 | <Queue id="testQueue"
625 | onProcess="processing => {
626 | if (processing.item === 'error') throw 'Test error';
627 | testState = testState || [];
628 | testState.push('processed-' + processing.item);
629 | }"
630 | onProcessError="(error, processing) => {
631 | testState = testState || [];
632 | testState.push('error-' + processing.item);
633 | }" />
634 | <Button id="enqueueBtn" label="Enqueue" onClick="testQueue.enqueueItems(['good1', 'error', 'good2']);" />
635 | </Fragment>
636 | `);
637 |
638 | const buttonDriver = await createButtonDriver("enqueueBtn");
639 | await buttonDriver.component.click();
640 |
641 | await expect
642 | .poll(testStateDriver.testState)
643 | .toEqual(["processed-good1", "error-error", "processed-good2"]);
644 | });
645 |
646 | test("handles very large queue operations", async ({ initTestBed, createButtonDriver }) => {
647 | const { testStateDriver } = await initTestBed(`
648 | <Fragment>
649 | <Queue id="testQueue" onProcess="processing => { /* process item */ }" />
650 | <Button id="largeBtn" label="Large Op" onClick="
651 | // Create large array of items
652 | const largeArray = [];
653 | for (let i = 0; i < 100; i++) {
654 | largeArray.push('item-' + i);
655 | }
656 |
657 | const itemIds = testQueue.enqueueItems(largeArray);
658 | // Check uniqueness without Set
659 | const uniqueCheck = {};
660 | let allUnique = true;
661 | for (let i = 0; i < itemIds.length; i++) {
662 | if (uniqueCheck[itemIds[i]]) {
663 | allUnique = false;
664 | break;
665 | }
666 | uniqueCheck[itemIds[i]] = true;
667 | }
668 | testState = {
669 | enqueuedCount: itemIds.length,
670 | queueLength: testQueue.getQueueLength(),
671 | allUniqueIds: allUnique
672 | };
673 | " />
674 | </Fragment>
675 | `);
676 |
677 | const buttonDriver = await createButtonDriver("largeBtn");
678 | await buttonDriver.component.click();
679 |
680 | await expect.poll(testStateDriver.testState).toEqual({
681 | enqueuedCount: 100,
682 | queueLength: expect.any(Number), // Queue length will vary based on processing timing
683 | allUniqueIds: true,
684 | });
685 | });
686 |
687 | test("handles nested object and complex data types", async ({
688 | initTestBed,
689 | createButtonDriver,
690 | }) => {
691 | const { testStateDriver } = await initTestBed(`
692 | <Fragment>
693 | <Queue id="testQueue" onProcess="processing => testState = processing.item" />
694 | <Button id="complexBtn" label="Complex" onClick="
695 | const complexObject = {
696 | nested: { deep: { value: 'test' } },
697 | array: [1, 2, { key: 'value' }],
698 | dateStr: '2025-08-07',
699 | pattern: 'test'
700 | };
701 | testQueue.enqueueItem(complexObject);
702 | " />
703 | </Fragment>
704 | `);
705 |
706 | const buttonDriver = await createButtonDriver("complexBtn");
707 | await buttonDriver.component.click();
708 |
709 | const result = await testStateDriver.testState();
710 | expect(result.nested.deep.value).toBe("test");
711 | expect(result.array).toEqual([1, 2, { key: "value" }]);
712 | });
713 |
714 | test("handles rapid state changes during processing", async ({
715 | initTestBed,
716 | createButtonDriver,
717 | }) => {
718 | const { testStateDriver } = await initTestBed(`
719 | <Fragment>
720 | <Queue id="testQueue"
721 | onProcess="processing => {
722 | // Simulate processing time
723 | const start = Date.now();
724 | while (Date.now() - start < 10) {} // 10ms busy wait
725 | testState = (testState || 0) + 1;
726 | }" />
727 | <Button id="rapidBtn" label="Rapid" onClick="testQueue.enqueueItems([1, 2, 3, 4, 5]);" />
728 | </Fragment>
729 | `);
730 |
731 | const buttonDriver = await createButtonDriver("rapidBtn");
732 | await buttonDriver.component.click();
733 |
734 | await expect.poll(testStateDriver.testState).toBe(5);
735 | });
736 |
737 | test("handles context variables in templates correctly", async ({
738 | initTestBed,
739 | createButtonDriver,
740 | }) => {
741 | const { testStateDriver } = await initTestBed(`
742 | <Fragment>
743 | <Queue id="testQueue" clearAfterFinish="false"
744 | onProcess="processing => {
745 | processing.reportProgress(processing.item);
746 | testState = {
747 | queuedItems: testQueue.getQueuedItems().length,
748 | item: processing.item
749 | };
750 | }">
751 | <property name="progressFeedback">
752 | <Text value="Processing: {$queuedItems.length - $completedItems.length} remaining of {$queuedItems.length} total" />
753 | </property>
754 | <property name="resultFeedback">
755 | <Text value="Final: {$completedItems.length} completed, {$queuedItems.length} total" />
756 | </property>
757 | </Queue>
758 | <Button id="templateBtn" label="Test Templates" onClick="testQueue.enqueueItems(['item1', 'item2']);" />
759 | </Fragment>
760 | `);
761 |
762 | const buttonDriver = await createButtonDriver("templateBtn");
763 | await buttonDriver.component.click();
764 |
765 | // Verify that the queue processing works with template properties defined
766 | await expect.poll(testStateDriver.testState).toEqual({
767 | queuedItems: expect.any(Number),
768 | item: expect.any(String),
769 | });
770 | });
771 | });
772 |
```
--------------------------------------------------------------------------------
/xmlui/conventions/testing-conventions.md:
--------------------------------------------------------------------------------
```markdown
1 | # XMLUI Component Testing Conventions
2 |
3 | This document outlines the testing conventions and standards for XMLUI components using Playwright for end-to-end testing.
4 |
5 | ## Test Categories
6 |
7 | XMLUI components must be tested across four categories:
8 |
9 | 1. **Basic Functionality** - Core behavior but also test cases for each property, event, exposed method of the component. Each such item can be it's own category, if there are sufficiently numerous test cases for it (like >= 5, but that's not a hard requirement). It might make sense to combine multiple properties into the group, like `icon` and `iconPosition` under the `icon prop` group. For the props, also handle edge cases like null, undefined, unexpected input types like objects where text is expected for props, invalid values (like -5 pixels for a width prop), specially long unicode characters like 👨👩👧👦, or chinese characters. For interactive componenets, test interactivity with keyboard and mouse. For Layout Components, test arrangement, spacing
10 |
11 | ```typescript
12 | test("renders with basic props", async ({ initTestBed, page }) => {
13 | await initTestBed(`<ComponentName size="sm" variant="primary"/>`);
14 | await expect(page.getByTestId("component")).toBeVisible();
15 | });
16 | ```
17 |
18 | 2. **Accessibility**
19 |
20 | Has ARIA attributes, keyboard navigation and conforms to Accessibility standards.
21 | These tests are partially to conform to WCAG and to reach a better score on benchmarks for them.
22 |
23 | ```typescript
24 | test("has correct accessibility attributes", async ({ initTestBed, page }) => {
25 | await initTestBed(`<ComponentName label="Test Label"/>`);
26 | const component = page.getByLabel("Test Label");
27 | await expect(component).toHaveRole("button");
28 | });
29 | ```
30 |
31 | 3. **Theme Variables** - CSS custom properties and fallback behavior. Use exact browser values: `"rgb(255, 0, 0)"` not `"red"`. **Only include this category for components that support theme variables** (check component metadata for theme variable documentation).
32 |
33 | ```typescript
34 | test("applies theme variables", async ({ initTestBed, page }) => {
35 | await initTestBed(`<ComponentName/>`, {
36 | testThemeVars: { "backgroundColor-ComponentName": "rgb(255, 0, 0)" },
37 | });
38 | await expect(page.getByTestId("component")).toHaveCSS("background-color", "rgb(255, 0, 0)");
39 | });
40 | ```
41 |
42 | 4. **Other Edge Cases** - These are the test cases that fall into the edge cases category, but not within one particular property. So a button with an object variant should still live in the basic functionality category, within the variant prop group. Here could be a test for a datepicker stating that it covers the text below it (the text is not visible), when the user clicks on it, because the detepicker menu appears. This doesn't belong to a property, but is still an edge case.
43 |
44 | ```typescript
45 | test("handles no props gracefully", async ({ initTestBed, page }) => {
46 | await initTestBed(`<ComponentName/>`);
47 | await expect(page.getByTestId("component")).toBeVisible();
48 | });
49 | ```
50 |
51 | ## File Organization
52 |
53 | - **Location**: Test files MUST be in the same directory as the component's implementation.
54 | - **Naming**: Use `ComponentName.spec.ts` format
55 | - **Import**: `import { test, expect } from "../../testing/fixtures";`
56 |
57 | ## Context Information
58 |
59 | Xmlui is a declarative, reactive, component based web framework.
60 |
61 | Checkbox.spec.ts is an excellent testing file with good examples, and it's a great resource on how to write tests.
62 |
63 | ### Documentation location
64 |
65 | There's documentation for the components, which we call metadata. They lives in the component's file, like `Button.tsx` next to the testing file `Button.spec.ts`, but not in the native implementation file `ButtonNative.tsx`. Generally, you should not read or rely on the native implementation details.
66 | There's also documentation in the `.md` files, next to the component's file.
67 |
68 | #### Component Metadata (Required Reading)
69 |
70 | **ALWAYS read the component's `.tsx` file before creating tests.** The component files contain essential metadata that documents:
71 |
72 | - **Properties**: All available props with their types and descriptions
73 | - **Events**: Available event handlers and their parameters
74 | - **Theme Variables**: Default values and names for CSS custom properties used in theme testing
75 |
76 | Use the documented theme variable names when creating theme tests instead of guessing:
77 |
78 | ```typescript
79 | // ✅ CORRECT - Use documented theme variable names from metadata
80 | test("applies theme variables", async ({ initTestBed, page }) => {
81 | await initTestBed(`<Button/>`, {
82 | testThemeVars: { "backgroundColor-Button": "rgb(255, 0, 0)" }, // From metadata
83 | });
84 | await expect(page.getByTestId("component")).toHaveCSS("background-color", "rgb(255, 0, 0)");
85 | });
86 | ```
87 |
88 | #### Documentation Files (Highly Recommended)
89 |
90 | **ALWAYS check for and read the component's `.md` documentation file** in the same directory. This documentation:
91 |
92 | - **Merges with metadata** to provide complete component information
93 | - **Contains comprehensive examples** showing real usage patterns
94 | - **Documents advanced features** and edge cases
95 | - **Provides context** for proper testing scenarios
96 |
97 | Example documentation locations:
98 |
99 | - `Button.tsx` - Contains metadata
100 | - `Button.md` - Contains examples and detailed usage
101 | - `Button.spec.ts` - Your test file
102 |
103 | **Best Practice**: Read both the `.tsx` metadata AND the `.md` documentation before writing any tests to ensure comprehensive coverage of all documented features.
104 |
105 | ### Commands
106 |
107 | These commands should be ran inside the npm package named `xmlui`, which is inside the directory called `xmlui`.
108 | There might be a root level directory called `xmlui` (a monorepo) which contains the `xmlui` subdirectory. You want to run the commands from the `xmlui` subdirectory.
109 |
110 | ```bash
111 | # Standard execution
112 | npx playwright test ComponentName.spec.ts
113 |
114 | # Category-specific
115 | npx playwright test ComponentName.spec.ts --grep "accessibility"
116 |
117 | # Development (recommended during creation) - prevents HTML report auto-opening
118 | npx playwright test ComponentName.spec.ts --reporter=line
119 |
120 | # Single worker for debugging (prevents race conditions)
121 | npx playwright test ComponentName.spec.ts --workers=1
122 |
123 | # Fast feedback during development (single worker + line reporter)
124 | npx playwright test ComponentName.spec.ts --workers=1 --reporter=line
125 | ```
126 |
127 | ### Timeout Configuration
128 |
129 | XMLUI uses optimized timeout settings for faster feedback during development:
130 |
131 | - **Expect timeout**: 1000ms (1 second) - How long to wait for assertions like `expect.poll()`
132 | - **Test timeout**: 5 seconds - Maximum time for entire test execution
133 | - **Global timeout**: Configured in `playwright.config.ts`
134 |
135 | These settings ensure tests fail quickly when conditions aren't met, providing rapid feedback during development. The shorter expect timeout helps identify issues faster than the default 5-second timeout.
136 |
137 | **Important**: Never manually show the HTML report or wait for Ctrl+C during test development. Use `--reporter=line` to get immediate console feedback without browser interference.
138 |
139 | ### Development Testing Commands
140 |
141 | For comprehensive debugging and development, use these command combinations:
142 |
143 | ```bash
144 | # Best practice during test development - single worker + line reporter
145 | npx playwright test ComponentName.spec.ts --workers=1 --reporter=line
146 |
147 | # Debug specific failing tests only
148 | npx playwright test ComponentName.spec.ts --grep "test name pattern" --reporter=line
149 |
150 | # Run specific test categories during development
151 | npx playwright test ComponentName.spec.ts --grep "Basic Functionality" --reporter=line
152 | ```
153 |
154 | The line reporter provides detailed progress information and immediate feedback about test failures without opening browser windows or HTML reports, making it ideal for iterative test development.
155 |
156 | ### Event Handler Naming
157 |
158 | **ALWAYS use "on" prefix for event handlers:**
159 |
160 | ```typescript
161 | // ✅ CORRECT
162 | onClick = "testState = 'clicked'";
163 | onWillOpen = "testState = 'opening'";
164 |
165 | // ❌ INCORRECT
166 | click = "testState = 'clicked'";
167 | willOpen = "testState = 'opening'";
168 | ```
169 |
170 | **Event vs Handler distinction:**
171 |
172 | - **Event names** (no "on"): Used in `<event name="click">` tags
173 | - **Event handlers** (with "on"): Used as attributes `onClick="..."`
174 |
175 | ### Event Handler Parameters
176 |
177 | **ALWAYS use arrow function syntax:**
178 |
179 | ```typescript
180 | // ✅ CORRECT
181 | onExpandedChange = "arg => testState = arg";
182 | onClick = "event => testState = event.type";
183 |
184 | // ❌ INCORRECT - arguments object doesn't work
185 | onExpandedChange = "testState = arguments[0]";
186 | ```
187 |
188 | most of the time (when the event handler does not need to access the event object),
189 | you can omit the arrow function and write the handler body directly.
190 |
191 | ### XMLUI Script Limitations
192 |
193 | XMLUI scripts have important JavaScript syntax limitations that must be followed:
194 |
195 | **NO "new" operator support:**
196 |
197 | ```typescript
198 | // ❌ INCORRECT - "new" operator not supported
199 | throw new Error("test error");
200 | const items = new Set([1, 2, 3]);
201 | const date = new Date();
202 | const regex = new RegExp("pattern");
203 |
204 | // ✅ CORRECT - Use alternatives
205 | throw "test error"; // String-based error throwing
206 | // Manual uniqueness check instead of Set
207 | const uniqueCheck = {};
208 | let allUnique = true;
209 | for (let i = 0; i < items.length; i++) {
210 | if (uniqueCheck[items[i]]) {
211 | allUnique = false;
212 | break;
213 | }
214 | uniqueCheck[items[i]] = true;
215 | }
216 | // Use string literals for dates
217 | const dateStr = "2025-08-07";
218 | // Use string patterns instead of regex literals
219 | const pattern = "test";
220 | ```
221 |
222 | ### Non-Visual Component Testing
223 |
224 | For non-visual components (like Queue, DataStore), use Button click handlers to access APIs:
225 |
226 | ```typescript
227 | // ✅ CORRECT - Access APIs through Button onClick
228 | const { testStateDriver } = await initTestBed(`
229 | <Fragment>
230 | <Queue id="testQueue" />
231 | <Button onClick="testState = testQueue.enqueueItem('test')" />
232 | </Fragment>
233 | `);
234 |
235 | // ❌ INCORRECT - Script tags don't provide API access
236 | const { testStateDriver } = await initTestBed(`
237 | <Queue id="testQueue" />
238 | <script>testState = testQueue.enqueueItem('test')</script>
239 | `);
240 | ```
241 |
242 | **Important**: Non-visual components often require event handlers (like `onProcess`) to exhibit expected behavior. Without processing handlers, queue-like components may not retain items as expected.
243 |
244 | ### Template Properties
245 |
246 | **ALWAYS wrap template properties in `<property>` tags:**
247 |
248 | ```typescript
249 | // ✅ CORRECT
250 | <ComponentName>
251 | <property name="triggerTemplate">
252 | <Button>Custom Trigger</Button>
253 | </property>
254 | </ComponentName>
255 |
256 | // ❌ INCORRECT
257 | <ComponentName>
258 | <triggerTemplate>
259 | <Button>Custom Trigger</Button>
260 | </triggerTemplate>
261 | </ComponentName>
262 | ```
263 |
264 | ## Mandatory Test Structure
265 |
266 | Use `test.describe("category name")` to group test cases.
267 | Don't need a top level group that encompuses every test case.
268 |
269 | ```typescript
270 | // =============================================================================
271 | // BASIC FUNCTIONALITY TESTS
272 | // =============================================================================
273 |
274 | test.describe("Basic Functionality", () => {
275 | test("component renders with basic props", async ({ initTestBed, createComponentDriver }) => {
276 | // Test implementation
277 | });
278 | });
279 |
280 | // =============================================================================
281 | // ACCESSIBILITY TESTS
282 | // =============================================================================
283 |
284 | test.describe("Accessibility", () => {
285 | test("component has correct accessibility attributes", async ({ initTestBed, page }) => {
286 | // Test implementation
287 | });
288 | });
289 |
290 | // =============================================================================
291 | // THEME VARIABLE TESTS (Only for components that support theme variables)
292 | // =============================================================================
293 |
294 | test.describe("Theme Variables", () => {
295 | test("component applies theme variables", async ({ initTestBed, createComponentDriver }) => {
296 | // Test implementation
297 | });
298 | });
299 |
300 | // =============================================================================
301 | // OTHER EDGE CASE TESTS
302 | // =============================================================================
303 |
304 | test.describe("Other Edge Cases", () => {
305 | test("component handles null props gracefully", async ({ initTestBed }) => {
306 | // Test implementation
307 | });
308 | });
309 | ```
310 |
311 | ## Testing Framework
312 |
313 | ### Test Initialization
314 |
315 | **Always use XMLUI test function from `fixtures.ts`:**
316 |
317 | ```typescript
318 | import { test, expect } from "../../testing/fixtures";
319 |
320 | test("component renders correctly", async ({ initTestBed }) => {
321 | await initTestBed(`<ComponentName prop="value"/>`, {});
322 | // This will create a webpage where the whole content of it is the top level xmlui component,
323 | // specified in the initTestBed's first parameter as source code.
324 |
325 | // Test implementation
326 | });
327 | ```
328 |
329 | ### initTestBed Usage
330 |
331 | ```typescript
332 | // Basic usage
333 | await initTestBed(`<ComponentName prop="value"/>`, {});
334 |
335 | // With theme variables
336 | await initTestBed(`<ComponentName/>`, {
337 | testThemeVars: { "backgroundColor-ComponentName": "rgb(255, 0, 0)" },
338 | });
339 |
340 | // With test state for events
341 | const { testStateDriver } = await initTestBed(`
342 | <ComponentName onClick="testState = 'clicked'"/>
343 | `);
344 | ```
345 |
346 | ## Testing Approaches
347 |
348 | ### Obtaining a locator (element)
349 |
350 | Locate elements in a way a user would locate them guided by their intention. For example `const checkboxLocator = await page.getByRole('checkbox')`, rather than relying on internal structure like `page.locator("input").nth(2)`. If and only if there are more elements of the same type and it does not make sense to use text (because the test is not about labels or content text, or because the layout would change due to the presence of the content), prefer to use `testId`-s in the source code and use those test ids to locate the elements. You should aviod using `page.locator()` in test cases.
351 |
352 | If it is possible to test something at the time of locationg the element, prefer that. `page.getByRole` is the prime example of this, because it also tests for accessibility and intent (the user is not looking for a div with a certain class on it, they are looking for an image or a button). Avoid long selectors, that could be more readable by adding some assertions. For example, this is better:
353 |
354 | ```ts
355 | const cb = page.getByRole("checkbox");
356 | await expect(cb).toBeDisabled();
357 | ```
358 |
359 | and this is worse:
360 |
361 | ```ts
362 | const cb = page.getByRole("checkbox", { disabled: true });
363 | await expect(cb).toBeVisible();
364 | ```
365 |
366 | However, if there are multiple elements of the same type and an option could differentiate them and assertions would need to be written out after locating the element anyway, this approach is better than using test ids. For example, this is a good use of the `{disabled: true}` option:
367 |
368 | ```ts
369 | await initTestBed(`
370 | <Fragment>
371 | <Checkbox enabled="false"/>
372 | <Checkbox />
373 | </Fragment>`);
374 | const cb = page.getByRole("checkbox", { disabled: true });
375 | // do something with the disabled checkbox
376 | ```
377 |
378 | ### Event testing
379 |
380 | Sometimes you need to test events. The easiest way is to do something like this:
381 |
382 | Don't worry about how the testState is actually obtained. Just know that inside an event handler, you can write javascript and access the `testState` variable, which you can make assertions against later by polling that value. Just make it obvious that testState is actually changed, so use an obvious name like 'changed', or if you need to test the handler multiple times, use a counter like `onDidChange="testState = testState == null ? 1 : testState + 1"` Test state is initialized to be null.
383 |
384 | ```typescript
385 | test("click event fires on click", async ({ initTestBed, page }) => {
386 | const { testStateDriver } = await initTestBed(`<Button onClick="testState = 'clicked'"/>`);
387 |
388 | await page.getByRole("button").click();
389 | await expect.poll(testStateDriver.testState).toEqual("clicked");
390 | });
391 | ```
392 |
393 | **Testing API Returns vs Component State**: For non-visual components, focus on testing API return values rather than internal component state, as the latter may not behave as expected without proper event handlers:
394 |
395 | ```typescript
396 | // ✅ CORRECT - Test API return values
397 | test("enqueueItem returns valid ID", async ({ initTestBed, createButtonDriver }) => {
398 | const { testStateDriver } = await initTestBed(`
399 | <Fragment>
400 | <Queue id="testQueue" />
401 | <Button onClick="testState = { id: testQueue.enqueueItem('test'), hasId: true }" />
402 | </Fragment>
403 | `);
404 |
405 | const buttonDriver = await createButtonDriver("button");
406 | await buttonDriver.component.click();
407 |
408 | const result = await testStateDriver.testState();
409 | expect(result.id).toBeTruthy();
410 | expect(result.hasId).toBe(true);
411 | });
412 |
413 | // ❌ POTENTIALLY INCORRECT - Component state may not persist without handlers
414 | test("queue length increases", async ({ initTestBed, createButtonDriver }) => {
415 | // Without onProcess handler, queue might not retain items
416 | const { testStateDriver } = await initTestBed(`
417 | <Fragment>
418 | <Queue id="testQueue" />
419 | <Button onClick="testQueue.enqueueItem('test'); testState = testQueue.getQueueLength()" />
420 | </Fragment>
421 | `);
422 | // This test might fail if Queue doesn't retain items without processing
423 | });
424 | ```
425 |
426 | ### Writing actions and drivers
427 |
428 | Not all tests need actions. Some are just instantiating the webpage with a given component and then making assertions on that.
429 |
430 | Actions are the most varried part of writing component tests. If and only if the action is super simple and consists of one step, and the action's name gives clarity on what it is doing, should you use actions directly on the locators. For example, this is good, because it is clear what the actions are doing, as the `check` and `uncheck` actions are complete in themselves:
431 |
432 | ```ts
433 | const cb = page.getByRole("checkbox");
434 | await cb.check();
435 | await expect(cb).toBeChecked();
436 | await cb.uncheck();
437 | await expect(cb).not.toBeChecked();
438 | ```
439 |
440 | However, in any other case, you should use a component driver to encapsulate the logic of the action. For example:
441 |
442 | ```ts
443 | test("search filters option labels", async ({ initTestBed, page, createSelectDriver }) => {
444 | await initTestBed(`
445 | <Select searchable="true">
446 | <Option value="opt1" label="first"/>
447 | <Option value="opt2" label="second"/>
448 | <Option value="opt3" label="third"/>
449 | </FormItem>
450 | `);
451 | const select = page.getByRole("combobox");
452 | const driver = await createSelectDriver(select);
453 |
454 | await driver.searchFor("second");
455 | await driver.chooseFirstOption();
456 | await expect(select).toHaveValue("opt2");
457 | });
458 | ```
459 |
460 | In this case, if the `searchFor` method were to be substituted with clicking on the locator and typing into the input field the label text, that would not convey the intent of the action, which is searching. Actions should have componenet-specific names. For example, a Dropdown can have a `toggleDropdownVisibility` method, which is pretty much just a click, but with a more meaningful name. You would not call it `click`.
461 |
462 | Drivers are derived from a base ComponentDriver class. Not every component needs a driver, so don't just create one for each component. In fact, if you can avoid it, you should, as they are a layer of abstraction.
463 |
464 | ### Drivers for internal component structure
465 |
466 | In extremely rare cases, you need to access the DOM structure of the component. This is usually a sign that the component is not well designed, and lacking accessibility features. In these cases, if you are an AI express the best approach that has accessability baked in. For example, you might be asked to test a spinner's border. You cound use the internal structure of the top level component, but it's better to encourage the programmer to implement role="status" on the component, so you can use `page.getByRole("status")`.
467 | In some RARE cases, they are the right approach though.
468 | For example, if the component is purely visual and has no accessibility attributes, or we actually want to assert something about a particular DOM element (like an internal element's border has a certain width): you can use the driver to obtain the locator, but only for that.
469 |
470 | Drivers should NOT:
471 |
472 | - have expectations and assertions in them. Those belong to the test case. Instead, return the locator from a method (in case driver is used for obtaining internal structure) and assert against it in the test case.
473 | - reimplement the playwright API, such as having a `click` or `hover` method which just wraps the playwright API and executes it on one of it's internal element'.
474 |
475 | ### Writing assertions
476 |
477 | Use web-first assertions, like `await expect(checkboxLocator).not.toBeChecked()`. If there is a built-in assertion, prefer that over an assertion checking against a the dom attributes. This is an antipattern: `await expect(checkboxLocator).toHaveAttribute("type", "checkbox");` because it relies on the element being an input element.
478 |
479 | ## Test Naming & Patterns
480 |
481 | ### Naming Standards
482 |
483 | - Avoid using the "component" word in names, it's redundant
484 | - Use concrete property, event, api, attribute, etc.
485 |
486 | - ✅ `"renders with 'variant' property"`
487 | - ✅ `"has correct 'aria-clickable'"`
488 | - ✅ `"handles null and undefined 'variant' property"`
489 | - ❌ `"test component"` or `"basic test"`
490 |
491 | ## Best Practices
492 |
493 | ### Avoid Frontend Code in E2E Tests
494 |
495 | It is crucial to avoid importing frontend code into E2E tests, especially if it transitively imports stylesheets. This can lead to unexpected issues with the test runner and slow down test execution.
496 |
497 | For example, importing `defaultProps` from a component file like `ButtonNative.tsx` into a test file like `Button.spec.ts` is an anti-pattern. The component file likely imports SCSS or CSS files, which should not be part of the test environment.
498 |
499 | Instead, if you need to share data between your component and your tests, define it in a separate file that can be imported safely by both.
500 |
501 | ### Skipping tests
502 |
503 | #### Skipping For coverage
504 |
505 | Use `test.skip` for comprehensive coverage, when you know which tests you want to write, but you haven't gotten around to implementing them yet.
506 |
507 | ```typescript
508 | test.skip("placeholder defaults to 'example.com'", async ({ initTestBed, page }) => {});
509 | ```
510 |
511 | #### Skipping for any other reason
512 |
513 | There are other reasons to skip tests, such as when a feature is not yet implemented or when a bug is present.
514 | After becoming certain the test is well written, skip the test with the appropriate skip reason.
515 |
516 | ```typescript
517 | test.fixme(
518 | `label displayed for type 'autocomplete'`,
519 | SKIP_REASON.XMLUI_BUG(
520 | "There are two labels in Autocomplete: one is ours, the other comes from the wrapped component -> this results in an error",
521 | ),
522 | async ({ initTestBed, page }) => {
523 | await initTestBed(`<Autocomplete label="test" />`);
524 | await expect(page.getByLabel("test"));
525 | // ...rest of the test is not relevant
526 | },
527 | );
528 | ```
529 |
530 | ### Systematic Testing
531 |
532 | In case there would be a lot of duplication for testing a property that has the exact same structure, use parameterized tests.
533 |
534 | ```typescript
535 | // Data type testing
536 | [
537 | { label: "null", value: "'{null}'", expected: "" },
538 | { label: "string", value: "'test'", expected: "test" },
539 | { label: "integer", value: "'{123}'", expected: "123" },
540 | // more cases...
541 | ].forEach(({ label, value, expected }) => {
542 | test(`handles ${label} correctly`, async ({ initTestBed, page }) => {
543 | await initTestBed(`<ComponentName value=${value} testId="component"/>`);
544 | await expect(page.getByTestId("component")).toHaveText(expected);
545 | });
546 | });
547 | ```
548 |
549 | ## Good test case patterns
550 |
551 | ### Layout/Positioning Tests
552 |
553 | Components that support layout properties (like `labelPosition`, `direction`, positioning, sizing) should include tests that verify visual arrangement using the `getBounds` utility function.
554 |
555 | #### Best Practices for Layout Testing
556 |
557 | - **Import getBounds**: Import from `"../../testing/component-test-helpers"`
558 | - **Use descriptive coordinates**: Destructure specific properties like `{ left, right, top, bottom }`
559 | - **Test both directions**: Include RTL tests when direction affects layout
560 | - **Verify invalid values**: Test graceful handling of invalid layout properties
561 |
562 | #### Testing Element Positioning
563 |
564 | Use `getBounds()` to get element coordinates and verify relative positioning:
565 |
566 | ```typescript
567 | test("ComponentName appears at the correct side of ComponentName2", async ({
568 | initTestBed,
569 | page,
570 | }) => {
571 | await initTestBed(`
572 | <Fragment>
573 | <ComponentName testId="comp1" />
574 | <ComponentName2 testId="comp2" />
575 | <Fragment>
576 | `);
577 |
578 | const { left: comp1Left } = await getBounds(page.getByTestId("comp1"));
579 | const { right: comp2Right } = await getBounds(page.getByTestId("comp2"));
580 |
581 | expect(comp1Left).toBeLessThan(comp2Right);
582 | });
583 | ```
584 |
585 | #### Testing Directional Layout (RTL/LTR)
586 |
587 | Test layout behavior in both directions when applicable:
588 |
589 | ```typescript
590 | test("startText displays at beginning of input (rtl)", async ({ initTestBed, page }) => {
591 | await initTestBed(`<TextBox testId="input" direction="rtl" startText="$" />`);
592 |
593 | const { left: compLeft, right: compRight } = await getBounds(page.getByTestId("input"));
594 | const { left: textLeft, right: textRight } = await getBounds(page.getByText("$"));
595 |
596 | await expect(page.getByTestId("input")).toContainText("$");
597 | expect(textRight - compLeft).toBeGreaterThanOrEqual(compRight - textLeft);
598 | });
599 | ```
600 |
601 | #### Testing Size Properties
602 |
603 | Verify width, height, and other sizing properties:
604 |
605 | ```typescript
606 | test("labelWidth applies custom label width", async ({ initTestBed, page }) => {
607 | const expected = 200;
608 | await initTestBed(`<InputComponent label="test test" labelWidth="${expected}px" />`);
609 | const { width } = await getBounds(page.getByText("test test"));
610 | expect(width).toEqual(expected);
611 | });
612 | ```
613 |
614 | #### Testing Complex Layout Arrangements
615 |
616 | For components with multiple positioned elements, test their relative arrangement:
617 |
618 | ```typescript
619 | test("all adornments appear in the right place", async ({ initTestBed, page }) => {
620 | await initTestBed(`
621 | <TextBox testId="input" startText="$" endText="USD" startIcon="search" endIcon="search" direction="ltr" />
622 | `);
623 | const { left: compLeft, right: compRight } = await getBounds(page.getByTestId("input"));
624 | const { left: startTextLeft, right: startTextRight } = await getBounds(page.getByText("$"));
625 | const { left: endTextLeft, right: endTextRight } = await getBounds(page.getByText("USD"));
626 | const { left: startIconLeft, right: startIconRight } = await getBounds(
627 | page.getByRole("img").first(),
628 | );
629 | const { left: endIconLeft, right: endIconRight } = await getBounds(page.getByRole("img").last());
630 |
631 | // Check order of adornments relative to their container component bounds
632 | expect(startTextRight - compLeft).toBeLessThanOrEqual(compRight - startTextLeft);
633 | expect(startIconRight - compLeft).toBeLessThanOrEqual(compRight - startIconLeft);
634 | expect(endTextRight - compLeft).toBeGreaterThanOrEqual(compRight - endTextLeft);
635 | expect(endIconRight - compLeft).toBeGreaterThanOrEqual(compRight - endIconLeft);
636 | });
637 | ```
638 |
639 | ### Testing Input Component API
640 |
641 | Test the following in a `test.describe("Api", () => {...})` block for input components (such as TextBox, Checkbox, Slider, etc.):
642 |
643 | - value
644 | - setValue
645 | - focus
646 |
647 | #### Example
648 |
649 | ```typescript
650 | test("component setValue API updates state", async ({ initTestBed, page }) => {
651 | await initTestBed(`
652 | <Fragment>
653 | <TextBox id="myTextBox" />
654 | <Button testId="setBtn" onClick="myTextBox.setValue('api value')" />
655 | </Fragment>
656 | `);
657 | await page.getByTestId("setBtn").click();
658 | await expect(page.getByRole("textbox")).toHaveValue("api value");
659 | });
660 | ```
661 |
662 | ## Bad test case patterns
663 |
664 | fill in later
665 |
```