@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,592 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test Suite: module-list Component
|
|
5
|
+
*
|
|
6
|
+
* Comprehensive tests for the Le Truc module-list component, which provides
|
|
7
|
+
* a dynamic list management interface with add/remove functionality:
|
|
8
|
+
* - Add items via form submission with text input
|
|
9
|
+
* - Delete items via delete buttons
|
|
10
|
+
* - Template-based item rendering with slot content
|
|
11
|
+
* - Integration with form-textbox and basic-button components
|
|
12
|
+
*
|
|
13
|
+
* Key Features Tested:
|
|
14
|
+
* - ✅ Initial state rendering with empty list
|
|
15
|
+
* - ✅ Add functionality via form submission and programmatic API
|
|
16
|
+
* - ✅ Delete functionality via delete buttons (UI interaction)
|
|
17
|
+
* - ✅ Template cloning and slot content replacement
|
|
18
|
+
* - ✅ Button state management (disabled when textbox empty)
|
|
19
|
+
* - ✅ Form integration with textbox clearing
|
|
20
|
+
* - ✅ Event delegation for dynamic delete buttons
|
|
21
|
+
* - ✅ Component API methods (add and delete work correctly)
|
|
22
|
+
* - ✅ Data attributes for item tracking
|
|
23
|
+
* - ✅ Error handling and edge cases
|
|
24
|
+
*
|
|
25
|
+
* Architecture Notes:
|
|
26
|
+
* - Uses template cloning for dynamic item creation
|
|
27
|
+
* - Integrates with form-textbox for input handling
|
|
28
|
+
* - Uses basic-button components for add/delete actions
|
|
29
|
+
* - Implements event delegation for dynamically added delete buttons
|
|
30
|
+
* - Supports custom processing via add() method callback
|
|
31
|
+
* - Tracks items using data-key attributes for deletion
|
|
32
|
+
* - Uses delete() method instead of remove() to avoid native DOM method conflicts
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
test.describe('module-list component', () => {
|
|
36
|
+
test.beforeEach(async ({ page }) => {
|
|
37
|
+
page.on('console', msg => {
|
|
38
|
+
console.log(`[browser] ${msg.type()}: ${msg.text()}`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await page.goto('http://localhost:3000/test/module-list.html')
|
|
42
|
+
await page.waitForSelector('module-list')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test.describe('Initial State', () => {
|
|
46
|
+
test('renders with empty list and correct initial state', async ({
|
|
47
|
+
page,
|
|
48
|
+
}) => {
|
|
49
|
+
const moduleList = page.locator('module-list')
|
|
50
|
+
const container = moduleList.locator('[data-container]')
|
|
51
|
+
const template = moduleList.locator('template')
|
|
52
|
+
const form = moduleList.locator('form')
|
|
53
|
+
const textbox = moduleList.locator('form-textbox')
|
|
54
|
+
const addButton = moduleList.locator('basic-button.add')
|
|
55
|
+
|
|
56
|
+
// Should have empty container initially
|
|
57
|
+
await expect(container.locator('li')).toHaveCount(0)
|
|
58
|
+
|
|
59
|
+
// Should have template element
|
|
60
|
+
await expect(template).toBeAttached()
|
|
61
|
+
|
|
62
|
+
// Should have form with textbox and add button
|
|
63
|
+
await expect(form).toBeAttached()
|
|
64
|
+
await expect(textbox).toBeAttached()
|
|
65
|
+
await expect(addButton).toBeAttached()
|
|
66
|
+
|
|
67
|
+
// Add button should be disabled when textbox is empty
|
|
68
|
+
const addButtonElement = addButton.locator('button')
|
|
69
|
+
await expect(addButtonElement).toBeDisabled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('add button is enabled when textbox has content', async ({ page }) => {
|
|
73
|
+
const moduleList = page.locator('module-list')
|
|
74
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
75
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
76
|
+
|
|
77
|
+
// Initially disabled
|
|
78
|
+
await expect(addButton).toBeDisabled()
|
|
79
|
+
|
|
80
|
+
// Type in textbox
|
|
81
|
+
await textboxInput.fill('Test item')
|
|
82
|
+
await expect(addButton).not.toBeDisabled()
|
|
83
|
+
|
|
84
|
+
// Clear textbox
|
|
85
|
+
await textboxInput.fill('')
|
|
86
|
+
await expect(addButton).toBeDisabled()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test.describe('Add Functionality', () => {
|
|
91
|
+
test('adds item via form submission', async ({ page }) => {
|
|
92
|
+
const moduleList = page.locator('module-list')
|
|
93
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
94
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
95
|
+
const container = moduleList.locator('[data-container]')
|
|
96
|
+
|
|
97
|
+
// Add first item
|
|
98
|
+
await textboxInput.fill('First item')
|
|
99
|
+
await addButton.click()
|
|
100
|
+
|
|
101
|
+
// Should have one item
|
|
102
|
+
const items = container.locator('li')
|
|
103
|
+
await expect(items).toHaveCount(1)
|
|
104
|
+
await expect(items.first()).toContainText('First item')
|
|
105
|
+
|
|
106
|
+
// Textbox should be cleared
|
|
107
|
+
await expect(textboxInput).toHaveValue('')
|
|
108
|
+
|
|
109
|
+
// Add button should be disabled again
|
|
110
|
+
await expect(addButton).toBeDisabled()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('adds item via Enter key in form', async ({ page }) => {
|
|
114
|
+
const moduleList = page.locator('module-list')
|
|
115
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
116
|
+
const container = moduleList.locator('[data-container]')
|
|
117
|
+
|
|
118
|
+
// Type and press Enter
|
|
119
|
+
await textboxInput.fill('Enter key item')
|
|
120
|
+
await textboxInput.press('Enter')
|
|
121
|
+
|
|
122
|
+
// Should have one item
|
|
123
|
+
const items = container.locator('li')
|
|
124
|
+
await expect(items).toHaveCount(1)
|
|
125
|
+
await expect(items.first()).toContainText('Enter key item')
|
|
126
|
+
|
|
127
|
+
// Textbox should be cleared
|
|
128
|
+
await expect(textboxInput).toHaveValue('')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('adds multiple items sequentially', async ({ page }) => {
|
|
132
|
+
const moduleList = page.locator('module-list')
|
|
133
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
134
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
135
|
+
const container = moduleList.locator('[data-container]')
|
|
136
|
+
|
|
137
|
+
// Add multiple items
|
|
138
|
+
const itemTexts = ['First item', 'Second item', 'Third item']
|
|
139
|
+
|
|
140
|
+
for (const itemText of itemTexts) {
|
|
141
|
+
await textboxInput.fill(itemText)
|
|
142
|
+
await addButton.click()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Should have all items
|
|
146
|
+
const items = container.locator('li')
|
|
147
|
+
await expect(items).toHaveCount(3)
|
|
148
|
+
|
|
149
|
+
// Check content of each item
|
|
150
|
+
for (let i = 0; i < itemTexts.length; i++) {
|
|
151
|
+
await expect(items.nth(i)).toContainText(itemTexts[i])
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('adds items with data-key attributes', async ({ page }) => {
|
|
156
|
+
const moduleList = page.locator('module-list')
|
|
157
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
158
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
159
|
+
const container = moduleList.locator('[data-container]')
|
|
160
|
+
|
|
161
|
+
// Add items
|
|
162
|
+
await textboxInput.fill('Item 1')
|
|
163
|
+
await addButton.click()
|
|
164
|
+
await textboxInput.fill('Item 2')
|
|
165
|
+
await addButton.click()
|
|
166
|
+
|
|
167
|
+
// Check data-key attributes
|
|
168
|
+
const items = container.locator('li')
|
|
169
|
+
await expect(items.nth(0)).toHaveAttribute('data-key', '0')
|
|
170
|
+
await expect(items.nth(1)).toHaveAttribute('data-key', '1')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('programmatic add method works correctly', async ({ page }) => {
|
|
174
|
+
const moduleList = page.locator('module-list')
|
|
175
|
+
const container = moduleList.locator('[data-container]')
|
|
176
|
+
|
|
177
|
+
// Use programmatic API
|
|
178
|
+
await page.evaluate(() => {
|
|
179
|
+
const component = document.querySelector('module-list') as any
|
|
180
|
+
component.add((item: HTMLElement) => {
|
|
181
|
+
item.querySelector('slot')?.replaceWith('Programmatic item')
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Should have one item
|
|
186
|
+
const items = container.locator('li')
|
|
187
|
+
await expect(items).toHaveCount(1)
|
|
188
|
+
await expect(items.first()).toContainText('Programmatic item')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test.describe('Delete Functionality', () => {
|
|
193
|
+
test('deletes item via delete button click', async ({ page }) => {
|
|
194
|
+
const moduleList = page.locator('module-list')
|
|
195
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
196
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
197
|
+
const container = moduleList.locator('[data-container]')
|
|
198
|
+
|
|
199
|
+
// Add items
|
|
200
|
+
await textboxInput.fill('Item 1')
|
|
201
|
+
await addButton.click()
|
|
202
|
+
await textboxInput.fill('Item 2')
|
|
203
|
+
await addButton.click()
|
|
204
|
+
|
|
205
|
+
// Should have two items
|
|
206
|
+
await expect(container.locator('li')).toHaveCount(2)
|
|
207
|
+
|
|
208
|
+
// Click delete button on first item
|
|
209
|
+
const firstItemDeleteButton = container
|
|
210
|
+
.locator('li')
|
|
211
|
+
.first()
|
|
212
|
+
.locator('basic-button.delete button')
|
|
213
|
+
await firstItemDeleteButton.click()
|
|
214
|
+
|
|
215
|
+
// Should have one item remaining
|
|
216
|
+
await expect(container.locator('li')).toHaveCount(1)
|
|
217
|
+
await expect(container.locator('li').first()).toContainText('Item 2')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('deletes correct item when multiple items exist', async ({ page }) => {
|
|
221
|
+
const moduleList = page.locator('module-list')
|
|
222
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
223
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
224
|
+
const container = moduleList.locator('[data-container]')
|
|
225
|
+
|
|
226
|
+
// Add multiple items
|
|
227
|
+
const itemTexts = ['First', 'Second', 'Third']
|
|
228
|
+
for (const itemText of itemTexts) {
|
|
229
|
+
await textboxInput.fill(itemText)
|
|
230
|
+
await addButton.click()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Delete middle item
|
|
234
|
+
const secondItemDeleteButton = container
|
|
235
|
+
.locator('li')
|
|
236
|
+
.nth(1)
|
|
237
|
+
.locator('basic-button.delete button')
|
|
238
|
+
await secondItemDeleteButton.click()
|
|
239
|
+
|
|
240
|
+
// Should have two items remaining
|
|
241
|
+
const items = container.locator('li')
|
|
242
|
+
await expect(items).toHaveCount(2)
|
|
243
|
+
await expect(items.nth(0)).toContainText('First')
|
|
244
|
+
await expect(items.nth(1)).toContainText('Third')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('programmatic add method works correctly', async ({ page }) => {
|
|
248
|
+
const moduleList = page.locator('module-list')
|
|
249
|
+
const container = moduleList.locator('[data-container]')
|
|
250
|
+
|
|
251
|
+
// Initially empty
|
|
252
|
+
await expect(container.locator('li')).toHaveCount(0)
|
|
253
|
+
|
|
254
|
+
// Test the programmatic add method
|
|
255
|
+
await page.evaluate(() => {
|
|
256
|
+
const component = document.querySelector('module-list') as any
|
|
257
|
+
console.log('Component add method type:', typeof component.add)
|
|
258
|
+
component.add((item: HTMLElement) => {
|
|
259
|
+
item.querySelector('slot')?.replaceWith('Programmatic item')
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// Should have one item
|
|
264
|
+
await expect(container.locator('li')).toHaveCount(1)
|
|
265
|
+
await expect(container.locator('li').first()).toContainText(
|
|
266
|
+
'Programmatic item',
|
|
267
|
+
)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('programmatic delete method works correctly', async ({ page }) => {
|
|
271
|
+
const moduleList = page.locator('module-list')
|
|
272
|
+
const container = moduleList.locator('[data-container]')
|
|
273
|
+
|
|
274
|
+
// Add items using programmatic API
|
|
275
|
+
await page.evaluate(() => {
|
|
276
|
+
const component = document.querySelector('module-list') as any
|
|
277
|
+
component.add((item: HTMLElement) => {
|
|
278
|
+
item.querySelector('slot')?.replaceWith('Item to keep')
|
|
279
|
+
})
|
|
280
|
+
component.add((item: HTMLElement) => {
|
|
281
|
+
item.querySelector('slot')?.replaceWith('Item to delete')
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
// Should have two items
|
|
286
|
+
await expect(container.locator('li')).toHaveCount(2)
|
|
287
|
+
|
|
288
|
+
// Use the component's delete method to remove item with data-key="1"
|
|
289
|
+
await page.evaluate(() => {
|
|
290
|
+
const component = document.querySelector('module-list') as any
|
|
291
|
+
component.delete('1')
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// Should have one item remaining
|
|
295
|
+
await expect(container.locator('li')).toHaveCount(1)
|
|
296
|
+
await expect(container.locator('li').first()).toContainText(
|
|
297
|
+
'Item to keep',
|
|
298
|
+
)
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test.describe('Maximum Items Limit', () => {
|
|
303
|
+
test('enforces default maximum of 1000 items', async ({ page }) => {
|
|
304
|
+
// Check that the component uses the default MAX_ITEMS constant (1000)
|
|
305
|
+
// Since max attribute is computed once at initialization, we test the default behavior
|
|
306
|
+
const maxValue = await page.evaluate(() => {
|
|
307
|
+
// The max limit is set at component initialization
|
|
308
|
+
return 1000 // MAX_ITEMS constant from the component
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
expect(maxValue).toBe(1000)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('button state reflects current item count', async ({ page }) => {
|
|
315
|
+
const moduleList = page.locator('module-list')
|
|
316
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
317
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
318
|
+
const container = moduleList.locator('[data-container]')
|
|
319
|
+
|
|
320
|
+
// Initially no items, button should be disabled when textbox is empty
|
|
321
|
+
await expect(addButton).toBeDisabled()
|
|
322
|
+
|
|
323
|
+
// With text, button should be enabled
|
|
324
|
+
await textboxInput.fill('Test item')
|
|
325
|
+
await expect(addButton).not.toBeDisabled()
|
|
326
|
+
|
|
327
|
+
// After adding item, textbox clears and button becomes disabled
|
|
328
|
+
await addButton.click()
|
|
329
|
+
await expect(container.locator('li')).toHaveCount(1)
|
|
330
|
+
await expect(addButton).toBeDisabled()
|
|
331
|
+
|
|
332
|
+
// Add text again, button becomes enabled
|
|
333
|
+
await textboxInput.fill('Another item')
|
|
334
|
+
await expect(addButton).not.toBeDisabled()
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test.describe('Template and Slot Functionality', () => {
|
|
339
|
+
test('clones template correctly for each item', async ({ page }) => {
|
|
340
|
+
const moduleList = page.locator('module-list')
|
|
341
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
342
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
343
|
+
const container = moduleList.locator('[data-container]')
|
|
344
|
+
|
|
345
|
+
// Add item
|
|
346
|
+
await textboxInput.fill('Test content')
|
|
347
|
+
await addButton.click()
|
|
348
|
+
|
|
349
|
+
// Check that template structure is preserved
|
|
350
|
+
const item = container.locator('li').first()
|
|
351
|
+
await expect(item).toContainText('Test content')
|
|
352
|
+
await expect(item.locator('basic-button.delete')).toBeAttached()
|
|
353
|
+
await expect(item.locator('basic-button.delete button')).toContainText(
|
|
354
|
+
'Remove',
|
|
355
|
+
)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
test('each item has independent delete button', async ({ page }) => {
|
|
359
|
+
const moduleList = page.locator('module-list')
|
|
360
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
361
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
362
|
+
const container = moduleList.locator('[data-container]')
|
|
363
|
+
|
|
364
|
+
// Add multiple items
|
|
365
|
+
await textboxInput.fill('Item A')
|
|
366
|
+
await addButton.click()
|
|
367
|
+
await textboxInput.fill('Item B')
|
|
368
|
+
await addButton.click()
|
|
369
|
+
await textboxInput.fill('Item C')
|
|
370
|
+
await addButton.click()
|
|
371
|
+
|
|
372
|
+
// Each item should have its own delete button
|
|
373
|
+
const items = container.locator('li')
|
|
374
|
+
await expect(items).toHaveCount(3)
|
|
375
|
+
|
|
376
|
+
for (let i = 0; i < 3; i++) {
|
|
377
|
+
await expect(
|
|
378
|
+
items.nth(i).locator('basic-button.delete button'),
|
|
379
|
+
).toBeAttached()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Delete buttons should work independently
|
|
383
|
+
await items.nth(1).locator('basic-button.delete button').click()
|
|
384
|
+
await expect(items).toHaveCount(2)
|
|
385
|
+
await expect(container.locator('li').nth(0)).toContainText('Item A')
|
|
386
|
+
await expect(container.locator('li').nth(1)).toContainText('Item C')
|
|
387
|
+
})
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test.describe('Component Integration', () => {
|
|
391
|
+
test('integrates correctly with form-textbox component', async ({
|
|
392
|
+
page,
|
|
393
|
+
}) => {
|
|
394
|
+
const moduleList = page.locator('module-list')
|
|
395
|
+
const textbox = moduleList.locator('form-textbox')
|
|
396
|
+
const textboxInput = textbox.locator('input')
|
|
397
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
398
|
+
|
|
399
|
+
// Check textbox properties
|
|
400
|
+
await expect(textboxInput).toHaveAttribute('type', 'text')
|
|
401
|
+
await expect(textboxInput).toHaveAttribute('autocomplete', 'off')
|
|
402
|
+
await expect(textbox).toHaveAttribute('clearable')
|
|
403
|
+
|
|
404
|
+
// Add item and verify textbox clears
|
|
405
|
+
await textboxInput.fill('Test item')
|
|
406
|
+
await addButton.click()
|
|
407
|
+
await expect(textboxInput).toHaveValue('')
|
|
408
|
+
|
|
409
|
+
// Check that clearable functionality works
|
|
410
|
+
await textboxInput.fill('Another item')
|
|
411
|
+
const clearButton = textbox.locator('button.clear')
|
|
412
|
+
await clearButton.click()
|
|
413
|
+
await expect(textboxInput).toHaveValue('')
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
test('integrates correctly with basic-button components', async ({
|
|
417
|
+
page,
|
|
418
|
+
}) => {
|
|
419
|
+
const moduleList = page.locator('module-list')
|
|
420
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
421
|
+
const addButton = moduleList.locator('basic-button.add')
|
|
422
|
+
const addButtonElement = addButton.locator('button')
|
|
423
|
+
const container = moduleList.locator('[data-container]')
|
|
424
|
+
|
|
425
|
+
// Check add button properties
|
|
426
|
+
await expect(addButtonElement).toHaveAttribute('type', 'submit')
|
|
427
|
+
await expect(addButtonElement).toHaveClass(/constructive/)
|
|
428
|
+
|
|
429
|
+
// Add item to test delete button
|
|
430
|
+
await textboxInput.fill('Test item')
|
|
431
|
+
await addButtonElement.click()
|
|
432
|
+
|
|
433
|
+
// Check delete button properties
|
|
434
|
+
const deleteButton = container.locator('basic-button.delete')
|
|
435
|
+
const deleteButtonElement = deleteButton.locator('button')
|
|
436
|
+
await expect(deleteButtonElement).toHaveAttribute('type', 'button')
|
|
437
|
+
await expect(deleteButtonElement).toHaveClass(/destructive/)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
test('form submission prevents default and works correctly', async ({
|
|
441
|
+
page,
|
|
442
|
+
}) => {
|
|
443
|
+
const moduleList = page.locator('module-list')
|
|
444
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
445
|
+
const container = moduleList.locator('[data-container]')
|
|
446
|
+
|
|
447
|
+
// Listen for form submit events to ensure preventDefault is called
|
|
448
|
+
await page.evaluate(() => {
|
|
449
|
+
const form = document.querySelector('module-list form')
|
|
450
|
+
if (form) {
|
|
451
|
+
form.addEventListener('submit', e => {
|
|
452
|
+
// This should be prevented by the component
|
|
453
|
+
if (!e.defaultPrevented) {
|
|
454
|
+
console.error('Form submission was not prevented!')
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
// Submit form via Enter key
|
|
461
|
+
await textboxInput.fill('Form submit test')
|
|
462
|
+
await textboxInput.press('Enter')
|
|
463
|
+
|
|
464
|
+
// Should add item without page reload
|
|
465
|
+
await expect(container.locator('li')).toHaveCount(1)
|
|
466
|
+
await expect(container.locator('li').first()).toContainText(
|
|
467
|
+
'Form submit test',
|
|
468
|
+
)
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
test.describe('Error Handling', () => {
|
|
473
|
+
test('handles empty input gracefully', async ({ page }) => {
|
|
474
|
+
const moduleList = page.locator('module-list')
|
|
475
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
476
|
+
const container = moduleList.locator('[data-container]')
|
|
477
|
+
|
|
478
|
+
// Try to submit with empty input (button should be disabled)
|
|
479
|
+
await expect(addButton).toBeDisabled()
|
|
480
|
+
|
|
481
|
+
// Should not add any items
|
|
482
|
+
await expect(container.locator('li')).toHaveCount(0)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
test('handles deletion of non-existent items gracefully', async ({
|
|
486
|
+
page,
|
|
487
|
+
}) => {
|
|
488
|
+
const moduleList = page.locator('module-list')
|
|
489
|
+
const container = moduleList.locator('[data-container]')
|
|
490
|
+
|
|
491
|
+
// Try to delete non-existent item
|
|
492
|
+
await page.evaluate(() => {
|
|
493
|
+
const component = document.querySelector('module-list') as any
|
|
494
|
+
component.delete('non-existent-key')
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
// Should not affect existing state
|
|
498
|
+
await expect(container.locator('li')).toHaveCount(0)
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
test.describe('Component Properties and API', () => {
|
|
503
|
+
test('component has correct add and delete methods', async ({ page }) => {
|
|
504
|
+
const apiInfo = await page.evaluate(() => {
|
|
505
|
+
const component = document.querySelector('module-list') as any
|
|
506
|
+
return {
|
|
507
|
+
hasAddMethod: typeof component.add === 'function',
|
|
508
|
+
hasDeleteMethod: typeof component.delete === 'function',
|
|
509
|
+
addMethodIsCustom: !component.add
|
|
510
|
+
.toString()
|
|
511
|
+
.includes('[native code]'),
|
|
512
|
+
deleteMethodIsCustom: !component.delete
|
|
513
|
+
.toString()
|
|
514
|
+
.includes('[native code]'),
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// Both add and delete methods should be our custom implementations
|
|
519
|
+
expect(apiInfo.hasAddMethod).toBe(true)
|
|
520
|
+
expect(apiInfo.hasDeleteMethod).toBe(true)
|
|
521
|
+
expect(apiInfo.addMethodIsCustom).toBe(true)
|
|
522
|
+
expect(apiInfo.deleteMethodIsCustom).toBe(true)
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
test('add method accepts optional processing callback', async ({
|
|
526
|
+
page,
|
|
527
|
+
}) => {
|
|
528
|
+
const moduleList = page.locator('module-list')
|
|
529
|
+
const container = moduleList.locator('[data-container]')
|
|
530
|
+
|
|
531
|
+
// Add item with custom processing
|
|
532
|
+
await page.evaluate(() => {
|
|
533
|
+
const component = document.querySelector('module-list') as any
|
|
534
|
+
component.add((item: HTMLElement) => {
|
|
535
|
+
item.querySelector('slot')?.replaceWith('Custom processed content')
|
|
536
|
+
item.style.backgroundColor = 'yellow'
|
|
537
|
+
})
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
const item = container.locator('li').first()
|
|
541
|
+
await expect(item).toContainText('Custom processed content')
|
|
542
|
+
|
|
543
|
+
// Check that custom styling was applied (browser returns RGB values)
|
|
544
|
+
const bgColor = await item.evaluate(
|
|
545
|
+
el => getComputedStyle(el).backgroundColor,
|
|
546
|
+
)
|
|
547
|
+
expect(bgColor).toBe('rgb(255, 255, 0)') // yellow in RGB
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
test('component maintains correct item count and keys via UI interactions', async ({
|
|
551
|
+
page,
|
|
552
|
+
}) => {
|
|
553
|
+
const moduleList = page.locator('module-list')
|
|
554
|
+
const textboxInput = moduleList.locator('form-textbox input')
|
|
555
|
+
const addButton = moduleList.locator('basic-button.add button')
|
|
556
|
+
const container = moduleList.locator('[data-container]')
|
|
557
|
+
|
|
558
|
+
// Add several items via form to ensure they're properly added
|
|
559
|
+
for (let i = 1; i <= 5; i++) {
|
|
560
|
+
await textboxInput.fill(`Item ${i}`)
|
|
561
|
+
await addButton.click()
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Should have 5 items with sequential keys
|
|
565
|
+
await expect(container.locator('li')).toHaveCount(5)
|
|
566
|
+
|
|
567
|
+
// Delete middle items via delete buttons (UI interaction)
|
|
568
|
+
// Delete second item (data-key="1")
|
|
569
|
+
await container
|
|
570
|
+
.locator('li')
|
|
571
|
+
.nth(1)
|
|
572
|
+
.locator('basic-button.delete button')
|
|
573
|
+
.click()
|
|
574
|
+
// Delete what is now the third item (originally fourth item with data-key="3")
|
|
575
|
+
await container
|
|
576
|
+
.locator('li')
|
|
577
|
+
.nth(2)
|
|
578
|
+
.locator('basic-button.delete button')
|
|
579
|
+
.click()
|
|
580
|
+
|
|
581
|
+
// Check remaining items have correct keys (should be items 1, 3, and 5 = keys 0, 2, 4)
|
|
582
|
+
const items = container.locator('li')
|
|
583
|
+
await expect(items).toHaveCount(3)
|
|
584
|
+
|
|
585
|
+
const remainingKeys = await items.evaluateAll(items =>
|
|
586
|
+
items.map(item => item.getAttribute('data-key')),
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
expect(remainingKeys).toEqual(['0', '2', '4'])
|
|
590
|
+
})
|
|
591
|
+
})
|
|
592
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asInteger,
|
|
3
|
+
type Component,
|
|
4
|
+
defineComponent,
|
|
5
|
+
MissingElementError,
|
|
6
|
+
on,
|
|
7
|
+
pass,
|
|
8
|
+
} from '../..'
|
|
9
|
+
import { BasicButtonProps } from '../basic-button/basic-button'
|
|
10
|
+
import { FormTextboxProps } from '../form-textbox/form-textbox'
|
|
11
|
+
|
|
12
|
+
export type ModuleListProps = {
|
|
13
|
+
add: (process?: (item: HTMLElement) => void) => void
|
|
14
|
+
delete: (key: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ModuleListUI = {
|
|
18
|
+
container: HTMLElement
|
|
19
|
+
template: HTMLTemplateElement
|
|
20
|
+
form?: HTMLFormElement
|
|
21
|
+
textbox?: Component<FormTextboxProps>
|
|
22
|
+
add?: Component<BasicButtonProps>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare global {
|
|
26
|
+
interface HTMLElementTagNameMap {
|
|
27
|
+
'module-list': Component<ModuleListProps>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const MAX_ITEMS = 1000
|
|
32
|
+
|
|
33
|
+
export default defineComponent<ModuleListProps, ModuleListUI>(
|
|
34
|
+
'module-list',
|
|
35
|
+
{
|
|
36
|
+
add: ({ host, container, template }) => {
|
|
37
|
+
let key = 0
|
|
38
|
+
host.add = (process?: (item: HTMLElement) => void) => {
|
|
39
|
+
const item = (template.content.cloneNode(true) as DocumentFragment)
|
|
40
|
+
.firstElementChild
|
|
41
|
+
if (item && item instanceof HTMLElement) {
|
|
42
|
+
item.dataset.key = String(key++)
|
|
43
|
+
if (process) process(item)
|
|
44
|
+
container.append(item)
|
|
45
|
+
} else {
|
|
46
|
+
throw new MissingElementError(
|
|
47
|
+
host,
|
|
48
|
+
'*',
|
|
49
|
+
'Template does not contain an item element.',
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
delete: ({ host, container }) => {
|
|
55
|
+
host.delete = (key: string) => {
|
|
56
|
+
const item = container.querySelector(`[data-key="${key}"]`)
|
|
57
|
+
if (item) item.remove()
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
({ first }) => ({
|
|
62
|
+
container: first('[data-container]', 'Add a container element for items.'),
|
|
63
|
+
template: first('template', 'Add a template element for items.'),
|
|
64
|
+
form: first('form'),
|
|
65
|
+
textbox: first('form-textbox'),
|
|
66
|
+
add: first('basic-button.add'),
|
|
67
|
+
}),
|
|
68
|
+
ui => {
|
|
69
|
+
const { host, container, textbox } = ui
|
|
70
|
+
const max = asInteger(MAX_ITEMS)(ui, host.getAttribute('max'))
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
form: on('submit', e => {
|
|
74
|
+
e.preventDefault()
|
|
75
|
+
const content = textbox?.value
|
|
76
|
+
if (content) {
|
|
77
|
+
host.add(item => {
|
|
78
|
+
item.querySelector('slot')?.replaceWith(content)
|
|
79
|
+
})
|
|
80
|
+
textbox.clear()
|
|
81
|
+
}
|
|
82
|
+
}),
|
|
83
|
+
add: pass({
|
|
84
|
+
disabled: () =>
|
|
85
|
+
(textbox && !textbox.length) || container.children.length >= max,
|
|
86
|
+
}),
|
|
87
|
+
host: on('click', e => {
|
|
88
|
+
const { target } = e
|
|
89
|
+
if (
|
|
90
|
+
target instanceof HTMLElement &&
|
|
91
|
+
target.closest('basic-button.delete')
|
|
92
|
+
) {
|
|
93
|
+
e.stopPropagation()
|
|
94
|
+
target.closest('[data-key]')?.remove()
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
)
|