@zeix/le-truc 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai-context.md +234 -0
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
- package/.editorconfig +12 -0
- package/.github/copilot-instructions.md +62 -0
- package/.github/workflows/codeql.yml +108 -0
- package/.github/workflows/static.yml +43 -0
- package/.prettierrc +17 -0
- package/CLAUDE.md +215 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +160 -0
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/biome.json +295 -0
- package/bun.lock +239 -0
- package/docs/about.html +105 -0
- package/docs/assets/main.css +1 -0
- package/docs/assets/main.js +10 -0
- package/docs/assets/main.js.map +66 -0
- package/docs/components.html +293 -0
- package/docs/data-flow.html +308 -0
- package/docs/examples/basic-button.html +367 -0
- package/docs/examples/basic-counter.html +188 -0
- package/docs/examples/basic-hello.html +138 -0
- package/docs/examples/basic-number.html +271 -0
- package/docs/examples/basic-pluralize.html +214 -0
- package/docs/examples/card-callout.html +152 -0
- package/docs/examples/card-mediaqueries.html +138 -0
- package/docs/examples/context-media.html +198 -0
- package/docs/examples/empty.html +37 -0
- package/docs/examples/form-checkbox.html +233 -0
- package/docs/examples/form-combobox.html +420 -0
- package/docs/examples/form-listbox.html +434 -0
- package/docs/examples/form-radiogroup.html +296 -0
- package/docs/examples/form-spinbutton.html +402 -0
- package/docs/examples/form-textbox.html +361 -0
- package/docs/examples/layout.html +67 -0
- package/docs/examples/module-carousel.html +552 -0
- package/docs/examples/module-catalog.html +241 -0
- package/docs/examples/module-codeblock.html +270 -0
- package/docs/examples/module-dialog.html +343 -0
- package/docs/examples/module-lazyload.html +289 -0
- package/docs/examples/module-list.html +197 -0
- package/docs/examples/module-pagination.html +283 -0
- package/docs/examples/module-scrollarea.html +447 -0
- package/docs/examples/module-tabgroup.html +526 -0
- package/docs/examples/module-todo.html +367 -0
- package/docs/examples/module-with-type.html +63 -0
- package/docs/examples/nested-components.html +88 -0
- package/docs/examples/recursive.html +56 -0
- package/docs/examples/simple-text.html +39 -0
- package/docs/examples/snippet.html +93 -0
- package/docs/examples/with-styles.html +75 -0
- package/docs/getting-started.html +143 -0
- package/docs/index.html +112 -0
- package/docs/sitemap.xml +28 -0
- package/docs/styling.html +160 -0
- package/docs/sw.js +112 -0
- package/docs-src/api/README.md +478 -0
- package/docs-src/api/_media/LICENSE +21 -0
- package/docs-src/api/classes/CircularDependencyError.md +299 -0
- package/docs-src/api/classes/CircularMutationError.md +301 -0
- package/docs-src/api/classes/ContextRequestEvent.md +590 -0
- package/docs-src/api/classes/DependencyTimeoutError.md +301 -0
- package/docs-src/api/classes/InvalidCallbackError.md +303 -0
- package/docs-src/api/classes/InvalidComponentNameError.md +295 -0
- package/docs-src/api/classes/InvalidCustomElementError.md +301 -0
- package/docs-src/api/classes/InvalidEffectsError.md +301 -0
- package/docs-src/api/classes/InvalidPropertyNameError.md +307 -0
- package/docs-src/api/classes/InvalidReactivesError.md +307 -0
- package/docs-src/api/classes/InvalidSignalValueError.md +303 -0
- package/docs-src/api/classes/MissingElementError.md +307 -0
- package/docs-src/api/classes/NullishSignalValueError.md +299 -0
- package/docs-src/api/classes/StoreKeyExistsError.md +303 -0
- package/docs-src/api/classes/StoreKeyRangeError.md +299 -0
- package/docs-src/api/classes/StoreKeyReadonlyError.md +303 -0
- package/docs-src/api/functions/asBoolean.md +21 -0
- package/docs-src/api/functions/asEnum.md +31 -0
- package/docs-src/api/functions/asInteger.md +39 -0
- package/docs-src/api/functions/asJSON.md +49 -0
- package/docs-src/api/functions/asNumber.md +37 -0
- package/docs-src/api/functions/asString.md +37 -0
- package/docs-src/api/functions/createCollection.md +83 -0
- package/docs-src/api/functions/createSensor.md +71 -0
- package/docs-src/api/functions/dangerouslySetInnerHTML.md +48 -0
- package/docs-src/api/functions/defineComponent.md +65 -0
- package/docs-src/api/functions/isCollection.md +37 -0
- package/docs-src/api/functions/isParser.md +41 -0
- package/docs-src/api/functions/match.md +47 -0
- package/docs-src/api/functions/on.md +58 -0
- package/docs-src/api/functions/pass.md +53 -0
- package/docs-src/api/functions/provideContexts.md +47 -0
- package/docs-src/api/functions/read.md +47 -0
- package/docs-src/api/functions/requestContext.md +51 -0
- package/docs-src/api/functions/resolve.md +40 -0
- package/docs-src/api/functions/runEffects.md +51 -0
- package/docs-src/api/functions/runElementEffects.md +57 -0
- package/docs-src/api/functions/schedule.md +33 -0
- package/docs-src/api/functions/setAttribute.md +48 -0
- package/docs-src/api/functions/setProperty.md +52 -0
- package/docs-src/api/functions/setStyle.md +48 -0
- package/docs-src/api/functions/setText.md +42 -0
- package/docs-src/api/functions/show.md +42 -0
- package/docs-src/api/functions/toSignal.md +37 -0
- package/docs-src/api/functions/toggleAttribute.md +48 -0
- package/docs-src/api/functions/toggleClass.md +48 -0
- package/docs-src/api/functions/updateElement.md +53 -0
- package/docs-src/api/globals.md +131 -0
- package/docs-src/api/type-aliases/Cleanup.md +27 -0
- package/docs-src/api/type-aliases/Collection.md +91 -0
- package/docs-src/api/type-aliases/CollectionListener.md +27 -0
- package/docs-src/api/type-aliases/Component.md +17 -0
- package/docs-src/api/type-aliases/ComponentProp.md +11 -0
- package/docs-src/api/type-aliases/ComponentProps.md +11 -0
- package/docs-src/api/type-aliases/ComponentSetup.md +31 -0
- package/docs-src/api/type-aliases/ComponentUI.md +27 -0
- package/docs-src/api/type-aliases/Computed.md +49 -0
- package/docs-src/api/type-aliases/ComputedCallback.md +29 -0
- package/docs-src/api/type-aliases/Context.md +33 -0
- package/docs-src/api/type-aliases/ContextType.md +19 -0
- package/docs-src/api/type-aliases/DangerouslySetInnerHTMLOptions.md +27 -0
- package/docs-src/api/type-aliases/DiffResult.md +61 -0
- package/docs-src/api/type-aliases/Effect.md +35 -0
- package/docs-src/api/type-aliases/EffectCallback.md +23 -0
- package/docs-src/api/type-aliases/Effects.md +21 -0
- package/docs-src/api/type-aliases/ElementEffects.md +21 -0
- package/docs-src/api/type-aliases/ElementFromKey.md +21 -0
- package/docs-src/api/type-aliases/ElementQueries.md +27 -0
- package/docs-src/api/type-aliases/ElementUpdater.md +131 -0
- package/docs-src/api/type-aliases/EventHandler.md +31 -0
- package/docs-src/api/type-aliases/EventType.md +17 -0
- package/docs-src/api/type-aliases/Fallback.md +21 -0
- package/docs-src/api/type-aliases/Initializers.md +21 -0
- package/docs-src/api/type-aliases/LooseReader.md +31 -0
- package/docs-src/api/type-aliases/MatchHandlers.md +77 -0
- package/docs-src/api/type-aliases/MaybeCleanup.md +23 -0
- package/docs-src/api/type-aliases/MaybeSignal.md +17 -0
- package/docs-src/api/type-aliases/Parser.md +39 -0
- package/docs-src/api/type-aliases/ParserOrFallback.md +21 -0
- package/docs-src/api/type-aliases/PassedProp.md +25 -0
- package/docs-src/api/type-aliases/PassedProps.md +21 -0
- package/docs-src/api/type-aliases/Reactive.md +25 -0
- package/docs-src/api/type-aliases/Reader.md +31 -0
- package/docs-src/api/type-aliases/ReservedWords.md +11 -0
- package/docs-src/api/type-aliases/ResolveResult.md +29 -0
- package/docs-src/api/type-aliases/SensorEvents.md +25 -0
- package/docs-src/api/type-aliases/Signal.md +41 -0
- package/docs-src/api/type-aliases/State.md +85 -0
- package/docs-src/api/type-aliases/Store.md +29 -0
- package/docs-src/api/type-aliases/UI.md +11 -0
- package/docs-src/api/type-aliases/UnknownContext.md +13 -0
- package/docs-src/api/variables/CONTEXT_REQUEST.md +11 -0
- package/docs-src/api/variables/UNSET.md +23 -0
- package/docs-src/api/variables/batch.md +25 -0
- package/docs-src/api/variables/createComputed.md +41 -0
- package/docs-src/api/variables/createEffect.md +35 -0
- package/docs-src/api/variables/createState.md +37 -0
- package/docs-src/api/variables/createStore.md +42 -0
- package/docs-src/api/variables/diff.md +43 -0
- package/docs-src/api/variables/isAbortError.md +33 -0
- package/docs-src/api/variables/isAsyncFunction.md +39 -0
- package/docs-src/api/variables/isComputed.md +37 -0
- package/docs-src/api/variables/isEqual.md +49 -0
- package/docs-src/api/variables/isFunction.md +39 -0
- package/docs-src/api/variables/isMutableSignal.md +37 -0
- package/docs-src/api/variables/isNumber.md +33 -0
- package/docs-src/api/variables/isRecord.md +39 -0
- package/docs-src/api/variables/isRecordOrArray.md +39 -0
- package/docs-src/api/variables/isSignal.md +37 -0
- package/docs-src/api/variables/isState.md +37 -0
- package/docs-src/api/variables/isStore.md +37 -0
- package/docs-src/api/variables/isString.md +33 -0
- package/docs-src/api/variables/isSymbol.md +33 -0
- package/docs-src/api/variables/toError.md +33 -0
- package/docs-src/api/variables/valueString.md +33 -0
- package/docs-src/includes/menu.html +44 -0
- package/docs-src/pages/about.md +89 -0
- package/docs-src/pages/components.md +437 -0
- package/docs-src/pages/data-flow.md +449 -0
- package/docs-src/pages/getting-started.md +170 -0
- package/docs-src/pages/index.md +98 -0
- package/docs-src/pages/styling.md +165 -0
- package/eslint.config.js +64 -0
- package/examples/_common/clear.ts +49 -0
- package/examples/_common/fetch.ts +160 -0
- package/examples/_common/focus.ts +45 -0
- package/examples/_common/highlight.ts +5 -0
- package/examples/_global.css +463 -0
- package/examples/basic-button/basic-button.css +176 -0
- package/examples/basic-button/basic-button.html +46 -0
- package/examples/basic-button/basic-button.spec.ts +160 -0
- package/examples/basic-button/basic-button.ts +45 -0
- package/examples/basic-button/copyToClipboard.ts +37 -0
- package/examples/basic-counter/basic-counter.css +21 -0
- package/examples/basic-counter/basic-counter.html +24 -0
- package/examples/basic-counter/basic-counter.spec.ts +85 -0
- package/examples/basic-counter/basic-counter.ts +43 -0
- package/examples/basic-hello/basic-hello.html +34 -0
- package/examples/basic-hello/basic-hello.spec.ts +110 -0
- package/examples/basic-hello/basic-hello.ts +36 -0
- package/examples/basic-number/basic-number.html +79 -0
- package/examples/basic-number/basic-number.spec.ts +175 -0
- package/examples/basic-number/basic-number.ts +124 -0
- package/examples/basic-pluralize/basic-pluralize.html +64 -0
- package/examples/basic-pluralize/basic-pluralize.spec.ts +258 -0
- package/examples/basic-pluralize/basic-pluralize.ts +82 -0
- package/examples/card-callout/card-callout.css +79 -0
- package/examples/card-callout/card-callout.html +5 -0
- package/examples/card-mediaqueries/card-mediaqueries.html +29 -0
- package/examples/card-mediaqueries/card-mediaqueries.spec.ts +300 -0
- package/examples/card-mediaqueries/card-mediaqueries.ts +41 -0
- package/examples/context-media/context-media.html +3 -0
- package/examples/context-media/context-media.ts +127 -0
- package/examples/form-checkbox/form-checkbox.css +70 -0
- package/examples/form-checkbox/form-checkbox.html +13 -0
- package/examples/form-checkbox/form-checkbox.spec.ts +357 -0
- package/examples/form-checkbox/form-checkbox.ts +50 -0
- package/examples/form-checkbox/vanilla-checkbox.ts +101 -0
- package/examples/form-combobox/form-combobox.css +118 -0
- package/examples/form-combobox/form-combobox.html +74 -0
- package/examples/form-combobox/form-combobox.spec.ts +977 -0
- package/examples/form-combobox/form-combobox.ts +128 -0
- package/examples/form-listbox/form-listbox.css +71 -0
- package/examples/form-listbox/form-listbox.html +67 -0
- package/examples/form-listbox/form-listbox.spec.ts +1050 -0
- package/examples/form-listbox/form-listbox.ts +196 -0
- package/examples/form-listbox/mocks/timezones.json +495 -0
- package/examples/form-radiogroup/form-radiogroup.css +87 -0
- package/examples/form-radiogroup/form-radiogroup.html +51 -0
- package/examples/form-radiogroup/form-radiogroup.spec.ts +515 -0
- package/examples/form-radiogroup/form-radiogroup.ts +58 -0
- package/examples/form-spinbutton/form-spinbutton.css +95 -0
- package/examples/form-spinbutton/form-spinbutton.html +96 -0
- package/examples/form-spinbutton/form-spinbutton.spec.ts +688 -0
- package/examples/form-spinbutton/form-spinbutton.ts +111 -0
- package/examples/form-textbox/form-textbox.css +104 -0
- package/examples/form-textbox/form-textbox.html +53 -0
- package/examples/form-textbox/form-textbox.spec.ts +542 -0
- package/examples/form-textbox/form-textbox.ts +104 -0
- package/examples/main.css +22 -0
- package/examples/main.ts +23 -0
- package/examples/module-carousel/module-carousel.css +113 -0
- package/examples/module-carousel/module-carousel.html +208 -0
- package/examples/module-carousel/module-carousel.spec.ts +523 -0
- package/examples/module-carousel/module-carousel.ts +131 -0
- package/examples/module-catalog/module-catalog.css +22 -0
- package/examples/module-catalog/module-catalog.html +82 -0
- package/examples/module-catalog/module-catalog.spec.ts +396 -0
- package/examples/module-catalog/module-catalog.ts +37 -0
- package/examples/module-codeblock/module-codeblock.css +95 -0
- package/examples/module-codeblock/module-codeblock.html +28 -0
- package/examples/module-codeblock/module-codeblock.ts +47 -0
- package/examples/module-demo/module-demo.css +13 -0
- package/examples/module-dialog/module-dialog.css +96 -0
- package/examples/module-dialog/module-dialog.html +66 -0
- package/examples/module-dialog/module-dialog.spec.ts +557 -0
- package/examples/module-dialog/module-dialog.ts +81 -0
- package/examples/module-lazyload/mocks/empty.html +1 -0
- package/examples/module-lazyload/mocks/module-with-type.html +27 -0
- package/examples/module-lazyload/mocks/nested-components.html +52 -0
- package/examples/module-lazyload/mocks/recursive.html +20 -0
- package/examples/module-lazyload/mocks/simple-text.html +3 -0
- package/examples/module-lazyload/mocks/snippet.html +57 -0
- package/examples/module-lazyload/mocks/with-styles.html +39 -0
- package/examples/module-lazyload/module-lazyload.html +132 -0
- package/examples/module-lazyload/module-lazyload.spec.ts +734 -0
- package/examples/module-lazyload/module-lazyload.ts +89 -0
- package/examples/module-list/module-list.html +30 -0
- package/examples/module-list/module-list.spec.ts +592 -0
- package/examples/module-list/module-list.ts +99 -0
- package/examples/module-pagination/module-pagination.css +79 -0
- package/examples/module-pagination/module-pagination.html +16 -0
- package/examples/module-pagination/module-pagination.spec.ts +701 -0
- package/examples/module-pagination/module-pagination.ts +88 -0
- package/examples/module-scrollarea/module-scrollarea.css +77 -0
- package/examples/module-scrollarea/module-scrollarea.html +189 -0
- package/examples/module-scrollarea/module-scrollarea.spec.ts +445 -0
- package/examples/module-scrollarea/module-scrollarea.ts +81 -0
- package/examples/module-tabgroup/module-tabgroup.css +55 -0
- package/examples/module-tabgroup/module-tabgroup.html +269 -0
- package/examples/module-tabgroup/module-tabgroup.spec.ts +631 -0
- package/examples/module-tabgroup/module-tabgroup.ts +102 -0
- package/examples/module-toc/module-toc.css +34 -0
- package/examples/module-todo/module-todo.css +84 -0
- package/examples/module-todo/module-todo.html +92 -0
- package/examples/module-todo/module-todo.spec.ts +528 -0
- package/examples/module-todo/module-todo.ts +91 -0
- package/examples/section-hero/section-hero.css +37 -0
- package/examples/section-menu/section-menu.css +81 -0
- package/examples/server.ts +95 -0
- package/examples/test-setup.md +314 -0
- package/index.dev.js +1688 -0
- package/index.dev.ts +127 -0
- package/index.js +3 -0
- package/index.js.map +42 -0
- package/index.ts +127 -0
- package/package.json +64 -0
- package/playwright.config.ts +31 -0
- package/server/BUILD_SYSTEM.md +428 -0
- package/server/SERVER.md +286 -0
- package/server/build.ts +91 -0
- package/server/config.ts +130 -0
- package/server/effects/api.ts +28 -0
- package/server/effects/css.ts +31 -0
- package/server/effects/examples.ts +109 -0
- package/server/effects/js.ts +32 -0
- package/server/effects/menu.ts +34 -0
- package/server/effects/pages.ts +178 -0
- package/server/effects/service-worker.ts +57 -0
- package/server/effects/sitemap.ts +27 -0
- package/server/file-signals.ts +361 -0
- package/server/file-watcher.ts +77 -0
- package/server/io.ts +174 -0
- package/server/layout-engine.ts +470 -0
- package/server/layout-utils.ts +615 -0
- package/server/layouts/api.html +76 -0
- package/server/layouts/base.html +37 -0
- package/server/layouts/blog.html +115 -0
- package/server/layouts/example.html +104 -0
- package/server/layouts/overview.html +165 -0
- package/server/layouts/page.html +36 -0
- package/server/layouts/test.html +24 -0
- package/server/markdoc-helpers.ts +217 -0
- package/server/markdoc.config.ts +29 -0
- package/server/schema/callout.markdoc.ts +17 -0
- package/server/schema/carousel.markdoc.ts +118 -0
- package/server/schema/demo.markdoc.ts +74 -0
- package/server/schema/fence.markdoc.ts +84 -0
- package/server/schema/heading.markdoc.ts +23 -0
- package/server/schema/hero.markdoc.ts +59 -0
- package/server/schema/section.markdoc.ts +10 -0
- package/server/schema/slide.markdoc.ts +17 -0
- package/server/schema/source.markdoc.ts +53 -0
- package/server/schema/tabgroup.markdoc.ts +102 -0
- package/server/serve.ts +635 -0
- package/server/templates/README.md +352 -0
- package/server/templates/constants.ts +236 -0
- package/server/templates/fragments.ts +159 -0
- package/server/templates/hmr.ts +269 -0
- package/server/templates/menu.ts +33 -0
- package/server/templates/performance-hints.ts +94 -0
- package/server/templates/service-worker.ts +403 -0
- package/server/templates/sitemap.ts +57 -0
- package/server/templates/toc.ts +41 -0
- package/server/templates/utils.ts +378 -0
- package/src/component.ts +215 -0
- package/src/context.ts +156 -0
- package/src/effects/attribute.ts +82 -0
- package/src/effects/class.ts +28 -0
- package/src/effects/event.ts +67 -0
- package/src/effects/html.ts +60 -0
- package/src/effects/method.ts +57 -0
- package/src/effects/pass.ts +103 -0
- package/src/effects/property.ts +57 -0
- package/src/effects/style.ts +34 -0
- package/src/effects/text.ts +28 -0
- package/src/effects.ts +412 -0
- package/src/errors.ts +160 -0
- package/src/parsers/boolean.ts +14 -0
- package/src/parsers/json.ts +33 -0
- package/src/parsers/number.ts +55 -0
- package/src/parsers/string.ts +32 -0
- package/src/parsers.ts +90 -0
- package/src/scheduler.ts +47 -0
- package/src/signals/collection.ts +253 -0
- package/src/signals/sensor.ts +131 -0
- package/src/ui.ts +236 -0
- package/src/util.ts +187 -0
- package/tsconfig.json +34 -0
- package/types/examples/basic-button/basic-button.d.ts +16 -0
- package/types/examples/basic-hello/basic-hello.d.ts +18 -0
- package/types/index.d.ts +27 -0
- package/types/index.dev.d.ts +27 -0
- package/types/src/collection.d.ts +27 -0
- package/types/src/component.d.ts +32 -0
- package/types/src/context.d.ts +85 -0
- package/types/src/effects/attribute.d.ts +23 -0
- package/types/src/effects/callMethod.d.ts +23 -0
- package/types/src/effects/class.d.ts +13 -0
- package/types/src/effects/dangerouslySetInnerHTML.d.ts +18 -0
- package/types/src/effects/event.d.ts +18 -0
- package/types/src/effects/html.d.ts +17 -0
- package/types/src/effects/method.d.ts +22 -0
- package/types/src/effects/pass.d.ts +18 -0
- package/types/src/effects/property.d.ts +22 -0
- package/types/src/effects/setAttribute.d.ts +24 -0
- package/types/src/effects/setProperty.d.ts +23 -0
- package/types/src/effects/setStyle.d.ts +14 -0
- package/types/src/effects/setText.d.ts +13 -0
- package/types/src/effects/style.d.ts +13 -0
- package/types/src/effects/text.d.ts +12 -0
- package/types/src/effects/toggleClass.d.ts +14 -0
- package/types/src/effects.d.ts +153 -0
- package/types/src/errors.d.ts +99 -0
- package/types/src/events.d.ts +27 -0
- package/types/src/extractors.d.ts +23 -0
- package/types/src/parsers/boolean.d.ts +10 -0
- package/types/src/parsers/json.d.ts +13 -0
- package/types/src/parsers/number.d.ts +21 -0
- package/types/src/parsers/string.d.ts +19 -0
- package/types/src/parsers.d.ts +41 -0
- package/types/src/scheduler.d.ts +11 -0
- package/types/src/sensor.d.ts +27 -0
- package/types/src/signals/collection.d.ts +32 -0
- package/types/src/signals/sensor.d.ts +27 -0
- package/types/src/ui.d.ts +37 -0
- package/types/src/util.d.ts +65 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* FORM-TEXTBOX COMPONENT TESTS
|
|
5
|
+
*
|
|
6
|
+
* Test Coverage Summary:
|
|
7
|
+
*
|
|
8
|
+
* ✅ WORKING FEATURES:
|
|
9
|
+
* - Initial state rendering with proper ARIA attributes
|
|
10
|
+
* - Value property now works correctly (writable, syncs to DOM)
|
|
11
|
+
* - Validation error handling works (depends on value property)
|
|
12
|
+
* - Writable properties (error, description, value) update correctly
|
|
13
|
+
* - Clear button and clear() method work correctly
|
|
14
|
+
* - Textarea value and length sensors work correctly
|
|
15
|
+
* - Character remaining count works for textarea with maxlength
|
|
16
|
+
* - Form integration works (native FormData collection)
|
|
17
|
+
* - DOM events fire correctly (input, change)
|
|
18
|
+
* - Property type validation
|
|
19
|
+
* - Readonly length property protection
|
|
20
|
+
*
|
|
21
|
+
* 🚫 BROKEN/LAZY FEATURES:
|
|
22
|
+
* - Input length sensor is lazy (only updates when watched)
|
|
23
|
+
*
|
|
24
|
+
* 📝 COMPONENT BEHAVIOR NOTES:
|
|
25
|
+
* - Value property works correctly with eager change event listeners
|
|
26
|
+
* - Length sensor is lazy (only updates when watched, like textarea description)
|
|
27
|
+
* - Validation works correctly thanks to eager change listeners
|
|
28
|
+
* - Value property is writable and can be set programmatically for autosuggest
|
|
29
|
+
* - DOM operations work fine throughout
|
|
30
|
+
*
|
|
31
|
+
* 🔧 TESTING APPROACH:
|
|
32
|
+
* - Tests validate current working behavior
|
|
33
|
+
* - Length sensor laziness is documented but not considered broken
|
|
34
|
+
* - Tests serve as regression suite for the working implementation
|
|
35
|
+
* - Comprehensive coverage of all component features
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
test.describe('form-textbox component', () => {
|
|
39
|
+
test.beforeEach(async ({ page }) => {
|
|
40
|
+
page.on('console', msg => {
|
|
41
|
+
console.log(`[browser] ${msg.type()}: ${msg.text()}`)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await page.goto('http://localhost:3000/test/form-textbox.html')
|
|
45
|
+
await page.waitForSelector('form-textbox')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ===== INITIAL STATE TESTS =====
|
|
49
|
+
|
|
50
|
+
test('renders initial state correctly', async ({ page }) => {
|
|
51
|
+
const textboxComponent = page.locator('form-textbox').first()
|
|
52
|
+
const input = textboxComponent.locator('input')
|
|
53
|
+
const label = textboxComponent.locator('label')
|
|
54
|
+
const description = textboxComponent.locator('.description')
|
|
55
|
+
|
|
56
|
+
// Should have empty value initially
|
|
57
|
+
await expect(input).toHaveValue('')
|
|
58
|
+
|
|
59
|
+
// Should display correct label and description
|
|
60
|
+
await expect(label).toHaveText('Name')
|
|
61
|
+
await expect(description).toHaveText(
|
|
62
|
+
'Tell us how you want us to call you in our communications.',
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Should have proper ARIA attributes
|
|
66
|
+
await expect(input).toHaveAttribute('aria-describedby', 'city-description')
|
|
67
|
+
await expect(input).not.toHaveAttribute('aria-errormessage')
|
|
68
|
+
await expect(input).toHaveAttribute('aria-invalid', 'false')
|
|
69
|
+
|
|
70
|
+
// Initial sensor property values
|
|
71
|
+
const state = await page.evaluate(() => {
|
|
72
|
+
const element = document.querySelector('form-textbox') as any
|
|
73
|
+
return { value: element.value, length: element.length }
|
|
74
|
+
})
|
|
75
|
+
expect(state.value).toBe('')
|
|
76
|
+
expect(state.length).toBe(0)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// ===== SENSOR BEHAVIOR TESTS =====
|
|
80
|
+
|
|
81
|
+
test('value updates on change event, length sensor is lazy', async ({
|
|
82
|
+
page,
|
|
83
|
+
}) => {
|
|
84
|
+
const input = page.locator('form-textbox input').first()
|
|
85
|
+
|
|
86
|
+
// Type some text
|
|
87
|
+
await input.type('John')
|
|
88
|
+
|
|
89
|
+
// Length sensor doesn't update (lazy - no watchers for input components)
|
|
90
|
+
let state = await page.evaluate(() => {
|
|
91
|
+
const element = document.querySelector('form-textbox') as any
|
|
92
|
+
return { value: element.value, length: element.length }
|
|
93
|
+
})
|
|
94
|
+
expect(state.length).toBe(0) // Lazy sensor - no watchers
|
|
95
|
+
expect(state.value).toBe('') // Value hasn't changed yet - needs change event
|
|
96
|
+
|
|
97
|
+
// Blur triggers change event
|
|
98
|
+
await input.blur()
|
|
99
|
+
|
|
100
|
+
// Now value should update
|
|
101
|
+
state = await page.evaluate(() => {
|
|
102
|
+
const element = document.querySelector('form-textbox') as any
|
|
103
|
+
return { value: element.value, length: element.length }
|
|
104
|
+
})
|
|
105
|
+
expect(state.value).toBe('John') // Now works!
|
|
106
|
+
expect(state.length).toBe(0) // Still 0 - no watchers for input
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('value property works, length sensor is lazy', async ({ page }) => {
|
|
110
|
+
const input = page.locator('form-textbox input').first()
|
|
111
|
+
|
|
112
|
+
// Type some text
|
|
113
|
+
await (input as any).type('test')
|
|
114
|
+
await input.blur()
|
|
115
|
+
|
|
116
|
+
// Value works, length is lazy
|
|
117
|
+
const state = await page.evaluate(() => {
|
|
118
|
+
const element = document.querySelector('form-textbox') as any
|
|
119
|
+
return { value: element.value, length: element.length }
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Value now works correctly!
|
|
123
|
+
expect(state.value).toBe('test')
|
|
124
|
+
expect(state.length).toBe(0) // Lazy sensor - no watchers
|
|
125
|
+
|
|
126
|
+
// The DOM input itself works fine
|
|
127
|
+
await expect(input).toHaveValue('test')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ===== VALIDATION TESTS =====
|
|
131
|
+
|
|
132
|
+
test('shows validation error on required field', async ({ page }) => {
|
|
133
|
+
const textboxComponent = page.locator('form-textbox').first()
|
|
134
|
+
const input = textboxComponent.locator('input')
|
|
135
|
+
const errorElement = textboxComponent.locator('.error')
|
|
136
|
+
|
|
137
|
+
// Initially no error
|
|
138
|
+
await expect(errorElement).toBeEmpty()
|
|
139
|
+
await expect(input).toHaveAttribute('aria-invalid', 'false')
|
|
140
|
+
|
|
141
|
+
// Fill and then clear to trigger validation
|
|
142
|
+
await input.fill('test')
|
|
143
|
+
await input.fill('')
|
|
144
|
+
|
|
145
|
+
// Manually trigger change event (blur doesn't automatically trigger change)
|
|
146
|
+
await page.evaluate(() => {
|
|
147
|
+
const inputEl = document.querySelector('form-textbox input')
|
|
148
|
+
inputEl?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Should show validation error
|
|
152
|
+
await expect(errorElement).not.toBeEmpty()
|
|
153
|
+
await expect(input).toHaveAttribute('aria-invalid', 'true')
|
|
154
|
+
await expect(input).toHaveAttribute('aria-errormessage', 'name-error')
|
|
155
|
+
|
|
156
|
+
// Error property should be set
|
|
157
|
+
const errorText = await page.evaluate(() => {
|
|
158
|
+
const element = document.querySelector('form-textbox') as any
|
|
159
|
+
return element.error
|
|
160
|
+
})
|
|
161
|
+
expect(errorText).toBeTruthy()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('clears error when valid input is provided', async ({ page }) => {
|
|
165
|
+
const textboxComponent = page.locator('form-textbox').first()
|
|
166
|
+
const input = textboxComponent.locator('input')
|
|
167
|
+
const errorElement = textboxComponent.locator('.error')
|
|
168
|
+
|
|
169
|
+
// Trigger validation error
|
|
170
|
+
await input.fill('test')
|
|
171
|
+
await input.fill('')
|
|
172
|
+
|
|
173
|
+
// Manually trigger change event for validation
|
|
174
|
+
await page.evaluate(() => {
|
|
175
|
+
const inputEl = document.querySelector('form-textbox input')
|
|
176
|
+
inputEl?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await expect(errorElement).not.toBeEmpty()
|
|
180
|
+
|
|
181
|
+
// Fill with valid input and trigger change
|
|
182
|
+
await input.fill('John Doe')
|
|
183
|
+
await page.evaluate(() => {
|
|
184
|
+
const inputEl = document.querySelector('form-textbox input')
|
|
185
|
+
inputEl?.dispatchEvent(new Event('change', { bubbles: true }))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// Error should clear
|
|
189
|
+
await expect(errorElement).toBeEmpty()
|
|
190
|
+
await expect(input).toHaveAttribute('aria-invalid', 'false')
|
|
191
|
+
await expect(input).not.toHaveAttribute('aria-errormessage')
|
|
192
|
+
|
|
193
|
+
const errorText = await page.evaluate(() => {
|
|
194
|
+
const element = document.querySelector('form-textbox') as any
|
|
195
|
+
return element.error
|
|
196
|
+
})
|
|
197
|
+
expect(errorText).toBe('')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// ===== TEXTAREA TESTS =====
|
|
201
|
+
|
|
202
|
+
test('handles textarea input', async ({ page }) => {
|
|
203
|
+
const textarea = page.locator('form-textbox textarea')
|
|
204
|
+
const testText = 'This is a comment\nwith multiple lines'
|
|
205
|
+
|
|
206
|
+
await textarea.fill(testText)
|
|
207
|
+
await textarea.blur() // Trigger change event
|
|
208
|
+
|
|
209
|
+
// Value updates, length sensor works because it has watchers
|
|
210
|
+
const value = await page.evaluate(() => {
|
|
211
|
+
const element = document.querySelectorAll('form-textbox')[2] as any
|
|
212
|
+
return { value: element.value, length: element.length }
|
|
213
|
+
})
|
|
214
|
+
expect(value.value).toBe(testText)
|
|
215
|
+
expect(value.length).toBe(testText.length)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('textarea value and length both work', async ({ page }) => {
|
|
219
|
+
const textarea = page.locator('form-textbox textarea')
|
|
220
|
+
const testText = 'This is a comment\nwith multiple lines'
|
|
221
|
+
|
|
222
|
+
await textarea.fill(testText)
|
|
223
|
+
await textarea.blur()
|
|
224
|
+
|
|
225
|
+
// DOM works fine
|
|
226
|
+
await expect(textarea).toHaveValue(testText)
|
|
227
|
+
|
|
228
|
+
// Both value and length work for textarea!
|
|
229
|
+
const state = await page.evaluate(() => {
|
|
230
|
+
const element = document.querySelectorAll('form-textbox')[2] as any
|
|
231
|
+
return { value: element.value, length: element.length }
|
|
232
|
+
})
|
|
233
|
+
expect(state.value).toBe(testText) // Now works!
|
|
234
|
+
expect(state.length).toBe(testText.length) // Works because has watchers!
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('shows remaining characters for maxlength textarea works', async ({
|
|
238
|
+
page,
|
|
239
|
+
}) => {
|
|
240
|
+
// Find the textarea component (third form-textbox)
|
|
241
|
+
const textareaComponent = page.locator('form-textbox').nth(2)
|
|
242
|
+
const textarea = textareaComponent.locator('textarea')
|
|
243
|
+
const description = textareaComponent.locator('.description')
|
|
244
|
+
|
|
245
|
+
// Initially should show full character count
|
|
246
|
+
await expect(description).toHaveText('500 characters remaining')
|
|
247
|
+
|
|
248
|
+
// Type some text (triggers input events for length sensor)
|
|
249
|
+
await textarea.type('Hello world')
|
|
250
|
+
|
|
251
|
+
// Description should update because length sensor works for textarea
|
|
252
|
+
await expect(description).toHaveText('489 characters remaining')
|
|
253
|
+
|
|
254
|
+
// Type more text
|
|
255
|
+
await textarea.fill('A'.repeat(100))
|
|
256
|
+
await expect(description).toHaveText('400 characters remaining')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// ===== CLEAR FUNCTIONALITY TESTS =====
|
|
260
|
+
|
|
261
|
+
test('clear button functionality works correctly', async ({ page }) => {
|
|
262
|
+
const clearableComponent = page.locator('form-textbox').nth(1)
|
|
263
|
+
const input = clearableComponent.locator('input')
|
|
264
|
+
const clearButton = clearableComponent.locator('button.clear')
|
|
265
|
+
|
|
266
|
+
// Clear button should be hidden initially
|
|
267
|
+
await expect(clearButton).toBeHidden()
|
|
268
|
+
|
|
269
|
+
// Type some text
|
|
270
|
+
await input.type('search terms')
|
|
271
|
+
|
|
272
|
+
// Clear button should become visible
|
|
273
|
+
await expect(clearButton).toBeVisible()
|
|
274
|
+
|
|
275
|
+
// Click clear button
|
|
276
|
+
await clearButton.click()
|
|
277
|
+
|
|
278
|
+
// Clear button functionality works - input is cleared
|
|
279
|
+
await expect(input).toHaveValue('')
|
|
280
|
+
|
|
281
|
+
// Clear button should be hidden again since input is empty
|
|
282
|
+
await expect(clearButton).toBeHidden()
|
|
283
|
+
|
|
284
|
+
// Component properties should be cleared
|
|
285
|
+
const value = await page.evaluate(() => {
|
|
286
|
+
const element = document.querySelectorAll('form-textbox')[1] as any
|
|
287
|
+
return { value: element.value, length: element.length }
|
|
288
|
+
})
|
|
289
|
+
expect(value.value).toBe('') // Clear works!
|
|
290
|
+
expect(value.length).toBe(0) // Length sensor is lazy
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('clear method works correctly', async ({ page }) => {
|
|
294
|
+
const input = page.locator('form-textbox input').nth(1)
|
|
295
|
+
|
|
296
|
+
// Fill input
|
|
297
|
+
await input.fill('test content')
|
|
298
|
+
await expect(input).toHaveValue('test content')
|
|
299
|
+
|
|
300
|
+
// Call clear method
|
|
301
|
+
await page.evaluate(() => {
|
|
302
|
+
const element = document.querySelectorAll('form-textbox')[1] as any
|
|
303
|
+
element.clear()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// Clear method works - DOM input is cleared
|
|
307
|
+
await expect(input).toHaveValue('')
|
|
308
|
+
|
|
309
|
+
// Check if clear method affects component properties
|
|
310
|
+
const value = await page.evaluate(() => {
|
|
311
|
+
const element = document.querySelectorAll('form-textbox')[1] as any
|
|
312
|
+
return { value: element.value, length: element.length }
|
|
313
|
+
})
|
|
314
|
+
expect(value.value).toBe('') // Clear method works!
|
|
315
|
+
expect(value.length).toBe(0) // Length sensor is lazy
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// ===== WRITABLE PROPERTY TESTS =====
|
|
319
|
+
|
|
320
|
+
test('updates description property programmatically', async ({ page }) => {
|
|
321
|
+
const textboxComponent = page.locator('form-textbox').first()
|
|
322
|
+
const description = textboxComponent.locator('.description')
|
|
323
|
+
|
|
324
|
+
// Initial description
|
|
325
|
+
await expect(description).toHaveText(
|
|
326
|
+
'Tell us how you want us to call you in our communications.',
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
// Update description property (this should work - it's a writable property)
|
|
330
|
+
await page.evaluate(() => {
|
|
331
|
+
const element = document.querySelector('form-textbox') as any
|
|
332
|
+
element.description = 'Updated description text'
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
await expect(description).toHaveText('Updated description text')
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('updates error property programmatically', async ({ page }) => {
|
|
339
|
+
const textboxComponent = page.locator('form-textbox').first()
|
|
340
|
+
const input = textboxComponent.locator('input')
|
|
341
|
+
const errorElement = textboxComponent.locator('.error')
|
|
342
|
+
|
|
343
|
+
// Initially no error
|
|
344
|
+
await expect(errorElement).toBeEmpty()
|
|
345
|
+
await expect(input).toHaveAttribute('aria-invalid', 'false')
|
|
346
|
+
|
|
347
|
+
// Set error property (this should work - it's a writable property)
|
|
348
|
+
await page.evaluate(() => {
|
|
349
|
+
const element = document.querySelector('form-textbox') as any
|
|
350
|
+
element.error = 'Custom error message'
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
await expect(errorElement).toHaveText('Custom error message')
|
|
354
|
+
await expect(input).toHaveAttribute('aria-invalid', 'true')
|
|
355
|
+
await expect(input).toHaveAttribute('aria-errormessage', 'name-error')
|
|
356
|
+
|
|
357
|
+
// Clear error
|
|
358
|
+
await page.evaluate(() => {
|
|
359
|
+
const element = document.querySelector('form-textbox') as any
|
|
360
|
+
element.error = ''
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
await expect(errorElement).toBeEmpty()
|
|
364
|
+
await expect(input).toHaveAttribute('aria-invalid', 'false')
|
|
365
|
+
await expect(input).not.toHaveAttribute('aria-errormessage')
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// ===== READONLY PROPERTY TESTS =====
|
|
369
|
+
|
|
370
|
+
test('value property is writable, length is readonly', async ({ page }) => {
|
|
371
|
+
const input = page.locator('form-textbox input').first()
|
|
372
|
+
|
|
373
|
+
// Type some text
|
|
374
|
+
await input.fill('test value')
|
|
375
|
+
await input.blur()
|
|
376
|
+
|
|
377
|
+
// Properties should reflect DOM state
|
|
378
|
+
let state = await page.evaluate(() => {
|
|
379
|
+
const element = document.querySelector('form-textbox') as any
|
|
380
|
+
return { value: element.value, length: element.length }
|
|
381
|
+
})
|
|
382
|
+
expect(state.value).toBe('test value') // Now works!
|
|
383
|
+
expect(state.length).toBe(0) // Lazy sensor - no watchers
|
|
384
|
+
|
|
385
|
+
// Value property is writable, length should be ignored
|
|
386
|
+
await page.evaluate(() => {
|
|
387
|
+
const element = document.querySelector('form-textbox') as any
|
|
388
|
+
element.value = 'changed value'
|
|
389
|
+
try {
|
|
390
|
+
element.length = 999 // This should be ignored
|
|
391
|
+
} catch (_e) {
|
|
392
|
+
// Expected - length should be readonly
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// Check that value was set and synced to DOM
|
|
397
|
+
await expect(input).toHaveValue('changed value')
|
|
398
|
+
|
|
399
|
+
// Properties should reflect the changes
|
|
400
|
+
state = await page.evaluate(() => {
|
|
401
|
+
const element = document.querySelector('form-textbox') as any
|
|
402
|
+
return { value: element.value, length: element.length }
|
|
403
|
+
})
|
|
404
|
+
expect(state.value).toBe('changed value') // Value is writable
|
|
405
|
+
expect(state.length).toBe(0) // Length remains 0 (readonly/lazy)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// ===== FORM INTEGRATION TESTS =====
|
|
409
|
+
|
|
410
|
+
test('handles form integration - DOM works fine', async ({ page }) => {
|
|
411
|
+
// Wrap first two components in a form (skip textarea to avoid extra field)
|
|
412
|
+
await page.evaluate(() => {
|
|
413
|
+
const form = document.createElement('form')
|
|
414
|
+
const textboxes = document.querySelectorAll('form-textbox')
|
|
415
|
+
// Only wrap first two to avoid the textarea
|
|
416
|
+
for (let i = 0; i < 2; i++) {
|
|
417
|
+
const textbox = textboxes[i]
|
|
418
|
+
if (textbox) {
|
|
419
|
+
textbox.parentNode?.insertBefore(form, textbox)
|
|
420
|
+
form.appendChild(textbox)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
const firstInput = page.locator('form-textbox input').first()
|
|
426
|
+
const secondInput = page.locator('form-textbox input').nth(1)
|
|
427
|
+
|
|
428
|
+
// Fill inputs
|
|
429
|
+
await firstInput.fill('John Doe')
|
|
430
|
+
await secondInput.fill('javascript react')
|
|
431
|
+
|
|
432
|
+
// Test form data (DOM-based, should work fine)
|
|
433
|
+
const formData = await page.evaluate(() => {
|
|
434
|
+
const form = document.querySelector('form')
|
|
435
|
+
if (!form) return null
|
|
436
|
+
const data = new FormData(form)
|
|
437
|
+
return Object.fromEntries(data.entries())
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
expect(formData).toEqual({
|
|
441
|
+
name: 'John Doe',
|
|
442
|
+
query: 'javascript react',
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
// ===== EVENT TESTS =====
|
|
447
|
+
|
|
448
|
+
test('fires input and change events correctly', async ({ page }) => {
|
|
449
|
+
// Set up event listeners
|
|
450
|
+
await page.evaluate(() => {
|
|
451
|
+
;(window as any).inputEventCount = 0
|
|
452
|
+
;(window as any).changeEventCount = 0
|
|
453
|
+
const input = document.querySelector('form-textbox input')
|
|
454
|
+
input?.addEventListener('input', () => {
|
|
455
|
+
;(window as any).inputEventCount++
|
|
456
|
+
})
|
|
457
|
+
input?.addEventListener('change', () => {
|
|
458
|
+
;(window as any).changeEventCount++
|
|
459
|
+
})
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const input = page.locator('form-textbox input').first()
|
|
463
|
+
|
|
464
|
+
// Type should fire input events
|
|
465
|
+
await input.type('test')
|
|
466
|
+
|
|
467
|
+
const inputCount = await page.evaluate(
|
|
468
|
+
() => (window as any).inputEventCount,
|
|
469
|
+
)
|
|
470
|
+
expect(inputCount).toBeGreaterThan(0)
|
|
471
|
+
|
|
472
|
+
// Blur should fire change event
|
|
473
|
+
await input.blur()
|
|
474
|
+
|
|
475
|
+
const changeCount = await page.evaluate(
|
|
476
|
+
() => (window as any).changeEventCount,
|
|
477
|
+
)
|
|
478
|
+
expect(changeCount).toBe(1)
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// ===== MAXLENGTH VALIDATION TESTS =====
|
|
482
|
+
|
|
483
|
+
test('validates maxlength on textarea - DOM and length sensor work', async ({
|
|
484
|
+
page,
|
|
485
|
+
}) => {
|
|
486
|
+
const textareaComponent = page.locator('form-textbox').nth(2)
|
|
487
|
+
const textarea = textareaComponent.locator('textarea')
|
|
488
|
+
const description = textareaComponent.locator('.description')
|
|
489
|
+
|
|
490
|
+
// Try to exceed maxlength
|
|
491
|
+
const longText = 'A'.repeat(600) // Exceeds 500 char limit
|
|
492
|
+
await textarea.fill(longText)
|
|
493
|
+
|
|
494
|
+
// Browser should limit to maxlength (DOM behavior works)
|
|
495
|
+
const actualValue = await textarea.inputValue()
|
|
496
|
+
expect(actualValue.length).toBeLessThanOrEqual(500)
|
|
497
|
+
|
|
498
|
+
// Description should show remaining characters (length sensor works for textarea)
|
|
499
|
+
const remainingText = await description.textContent()
|
|
500
|
+
expect(remainingText).toMatch(/[0-9]+ characters remaining/)
|
|
501
|
+
|
|
502
|
+
// Should show very few remaining characters
|
|
503
|
+
const remainingMatch = remainingText?.match(/(\d+) characters remaining/)
|
|
504
|
+
if (remainingMatch) {
|
|
505
|
+
expect(parseInt(remainingMatch[1])).toBeLessThanOrEqual(10)
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
// ===== PROPERTY TYPE TESTS =====
|
|
510
|
+
|
|
511
|
+
test('component properties exist with correct types', async ({ page }) => {
|
|
512
|
+
// Verify the component has the expected properties even if they don't work
|
|
513
|
+
const componentState = await page.evaluate(() => {
|
|
514
|
+
const element = document.querySelector('form-textbox') as any
|
|
515
|
+
return {
|
|
516
|
+
hasValue: 'value' in element,
|
|
517
|
+
hasLength: 'length' in element,
|
|
518
|
+
hasError: 'error' in element,
|
|
519
|
+
hasDescription: 'description' in element,
|
|
520
|
+
hasClear: 'clear' in element,
|
|
521
|
+
valueType: typeof element.value,
|
|
522
|
+
lengthType: typeof element.length,
|
|
523
|
+
errorType: typeof element.error,
|
|
524
|
+
descriptionType: typeof element.description,
|
|
525
|
+
clearType: typeof element.clear,
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
// Properties should exist with correct types
|
|
530
|
+
expect(componentState.hasValue).toBe(true)
|
|
531
|
+
expect(componentState.hasLength).toBe(true)
|
|
532
|
+
expect(componentState.hasError).toBe(true)
|
|
533
|
+
expect(componentState.hasDescription).toBe(true)
|
|
534
|
+
expect(componentState.hasClear).toBe(true)
|
|
535
|
+
|
|
536
|
+
expect(componentState.valueType).toBe('string')
|
|
537
|
+
expect(componentState.lengthType).toBe('number')
|
|
538
|
+
expect(componentState.errorType).toBe('string')
|
|
539
|
+
expect(componentState.descriptionType).toBe('string')
|
|
540
|
+
expect(componentState.clearType).toBe('function')
|
|
541
|
+
})
|
|
542
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
type ComponentUI,
|
|
4
|
+
createSensor,
|
|
5
|
+
defineComponent,
|
|
6
|
+
on,
|
|
7
|
+
read,
|
|
8
|
+
setAttribute,
|
|
9
|
+
setProperty,
|
|
10
|
+
setText,
|
|
11
|
+
} from '../..'
|
|
12
|
+
import { clearEffects, clearMethod } from '../_common/clear'
|
|
13
|
+
|
|
14
|
+
export type FormTextboxProps = {
|
|
15
|
+
value: string
|
|
16
|
+
readonly length: number
|
|
17
|
+
error: string
|
|
18
|
+
description: string
|
|
19
|
+
readonly clear: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type FormTextboxUI = {
|
|
23
|
+
textbox: HTMLInputElement | HTMLTextAreaElement
|
|
24
|
+
clear?: HTMLButtonElement
|
|
25
|
+
error?: HTMLElement
|
|
26
|
+
description?: HTMLElement
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
interface HTMLElementTagNameMap {
|
|
31
|
+
'form-textbox': Component<FormTextboxProps>
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default defineComponent<FormTextboxProps, FormTextboxUI>(
|
|
36
|
+
'form-textbox',
|
|
37
|
+
{
|
|
38
|
+
value: read(ui => ui.textbox.value, ''),
|
|
39
|
+
length: createSensor(
|
|
40
|
+
read(ui => ui.textbox.value.length, 0),
|
|
41
|
+
'textbox',
|
|
42
|
+
{
|
|
43
|
+
input: ({ target }) => target.value.length,
|
|
44
|
+
},
|
|
45
|
+
),
|
|
46
|
+
error: '',
|
|
47
|
+
description: ({
|
|
48
|
+
host,
|
|
49
|
+
description,
|
|
50
|
+
textbox,
|
|
51
|
+
}: ComponentUI<FormTextboxProps, FormTextboxUI>) => {
|
|
52
|
+
if (description) {
|
|
53
|
+
if (textbox && textbox.maxLength && description.dataset.remaining) {
|
|
54
|
+
return () =>
|
|
55
|
+
description.dataset.remaining!.replace(
|
|
56
|
+
'${n}',
|
|
57
|
+
String(textbox.maxLength - host.length),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
return description.textContent?.trim() ?? ''
|
|
61
|
+
} else {
|
|
62
|
+
return ''
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
clear: clearMethod,
|
|
66
|
+
},
|
|
67
|
+
({ first }) => ({
|
|
68
|
+
textbox: first(
|
|
69
|
+
'input, textarea',
|
|
70
|
+
'Add a native input or textarea as descendant element.',
|
|
71
|
+
),
|
|
72
|
+
clear: first('button.clear'),
|
|
73
|
+
error: first('.error'),
|
|
74
|
+
description: first('.description'),
|
|
75
|
+
}),
|
|
76
|
+
ui => {
|
|
77
|
+
const { host, textbox, error, description } = ui
|
|
78
|
+
const errorId = error?.id
|
|
79
|
+
const descriptionId = description?.id
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
textbox: [
|
|
83
|
+
on('change', () => {
|
|
84
|
+
textbox.checkValidity()
|
|
85
|
+
return {
|
|
86
|
+
value: textbox.value,
|
|
87
|
+
error: textbox.validationMessage,
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
setProperty('value'),
|
|
91
|
+
setProperty('ariaInvalid', () => String(!!host.error)),
|
|
92
|
+
setAttribute('aria-errormessage', () =>
|
|
93
|
+
host.error && errorId ? errorId : null,
|
|
94
|
+
),
|
|
95
|
+
setAttribute('aria-describedby', () =>
|
|
96
|
+
description && descriptionId ? descriptionId : null,
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
clear: clearEffects(ui),
|
|
100
|
+
error: setText('error'),
|
|
101
|
+
description: setText('description'),
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@import "./_global.css";
|
|
2
|
+
@import "./basic-button/basic-button.css";
|
|
3
|
+
@import "./basic-counter/basic-counter.css";
|
|
4
|
+
@import "./card-callout/card-callout.css";
|
|
5
|
+
@import "./form-checkbox/form-checkbox.css";
|
|
6
|
+
@import "./form-combobox/form-combobox.css";
|
|
7
|
+
@import "./form-listbox/form-listbox.css";
|
|
8
|
+
@import "./form-radiogroup/form-radiogroup.css";
|
|
9
|
+
@import "./form-spinbutton/form-spinbutton.css";
|
|
10
|
+
@import "./form-textbox/form-textbox.css";
|
|
11
|
+
@import "./module-carousel/module-carousel.css";
|
|
12
|
+
@import "./module-catalog/module-catalog.css";
|
|
13
|
+
@import "./module-codeblock/module-codeblock.css";
|
|
14
|
+
@import "./module-demo/module-demo.css";
|
|
15
|
+
@import "./module-dialog/module-dialog.css";
|
|
16
|
+
@import "./module-pagination/module-pagination.css";
|
|
17
|
+
@import "./module-scrollarea/module-scrollarea.css";
|
|
18
|
+
@import "./module-tabgroup/module-tabgroup.css";
|
|
19
|
+
@import "./module-toc/module-toc.css";
|
|
20
|
+
@import "./module-todo/module-todo.css";
|
|
21
|
+
@import "./section-hero/section-hero.css";
|
|
22
|
+
@import "./section-menu/section-menu.css";
|
package/examples/main.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import './basic-hello/basic-hello.ts'
|
|
2
|
+
import './basic-button/basic-button.ts'
|
|
3
|
+
import './basic-counter/basic-counter.ts'
|
|
4
|
+
import './basic-number/basic-number.ts'
|
|
5
|
+
import './basic-pluralize/basic-pluralize.ts'
|
|
6
|
+
import './card-mediaqueries/card-mediaqueries.ts'
|
|
7
|
+
import './context-media/context-media.ts'
|
|
8
|
+
import './form-checkbox/form-checkbox.ts'
|
|
9
|
+
import './form-combobox/form-combobox.ts'
|
|
10
|
+
import './form-listbox/form-listbox.ts'
|
|
11
|
+
import './form-radiogroup/form-radiogroup.ts'
|
|
12
|
+
import './form-spinbutton/form-spinbutton.ts'
|
|
13
|
+
import './form-textbox/form-textbox.ts'
|
|
14
|
+
import './module-carousel/module-carousel.ts'
|
|
15
|
+
import './module-catalog/module-catalog.ts'
|
|
16
|
+
import './module-codeblock/module-codeblock.ts'
|
|
17
|
+
import './module-dialog/module-dialog.ts'
|
|
18
|
+
import './module-lazyload/module-lazyload.ts'
|
|
19
|
+
import './module-list/module-list.ts'
|
|
20
|
+
import './module-pagination/module-pagination.ts'
|
|
21
|
+
import './module-scrollarea/module-scrollarea.ts'
|
|
22
|
+
import './module-tabgroup/module-tabgroup.ts'
|
|
23
|
+
import './module-todo/module-todo.ts'
|