diff --git a/.changeset/clean-toes-reflect.md b/.changeset/clean-toes-reflect.md new file mode 100644 index 000000000..6c6387817 --- /dev/null +++ b/.changeset/clean-toes-reflect.md @@ -0,0 +1,5 @@ +--- +"@shopware-ag/meteor-component-library": patch +--- + +Stop emitting onUpdate:modelValue event when blurring the mt-url-field diff --git a/.changeset/cool-kings-nail.md b/.changeset/cool-kings-nail.md new file mode 100644 index 000000000..0a206cf4f --- /dev/null +++ b/.changeset/cool-kings-nail.md @@ -0,0 +1,5 @@ +--- +"@shopware-ag/meteor-component-library": minor +--- + +Add types for event of mt-url-field component diff --git a/.changeset/large-apples-buy.md b/.changeset/large-apples-buy.md new file mode 100644 index 000000000..57cc39010 --- /dev/null +++ b/.changeset/large-apples-buy.md @@ -0,0 +1,5 @@ +--- +"@shopware-ag/meteor-component-library": patch +--- + +Add types for slots for mt-url-field diff --git a/.changeset/loud-donkeys-marry.md b/.changeset/loud-donkeys-marry.md new file mode 100644 index 000000000..0d069d6b2 --- /dev/null +++ b/.changeset/loud-donkeys-marry.md @@ -0,0 +1,5 @@ +--- +"@shopware-ag/meteor-component-library": patch +--- + +Announce tooltip content when focusing tooltip trigger diff --git a/.changeset/twelve-bulldogs-punch.md b/.changeset/twelve-bulldogs-punch.md new file mode 100644 index 000000000..d44944b36 --- /dev/null +++ b/.changeset/twelve-bulldogs-punch.md @@ -0,0 +1,5 @@ +--- +"@shopware-ag/meteor-component-library": patch +--- + +Stop announcing tooltip triangle to screen readers diff --git a/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-copy-to-clipboard-snap.png b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-copy-to-clipboard-snap.png new file mode 100644 index 000000000..f36abe566 Binary files /dev/null and b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-copy-to-clipboard-snap.png differ diff --git a/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-help-text-snap.png b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-help-text-snap.png new file mode 100644 index 000000000..51a21ce48 Binary files /dev/null and b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-help-text-snap.png differ diff --git a/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-hint-snap.png b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-hint-snap.png new file mode 100644 index 000000000..3b176a6ce Binary files /dev/null and b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-hint-snap.png differ diff --git a/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-placeholder-snap.png b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-placeholder-snap.png new file mode 100644 index 000000000..cbc044bde Binary files /dev/null and b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-placeholder-snap.png differ diff --git a/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-required-snap.png b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-required-snap.png new file mode 100644 index 000000000..495bc872f Binary files /dev/null and b/packages/component-library/__snapshots__/interaction-tests-form-mt-url-field--visual-test-required-snap.png differ diff --git a/packages/component-library/package.json b/packages/component-library/package.json index d7e062d45..193a0ff53 100644 --- a/packages/component-library/package.json +++ b/packages/component-library/package.json @@ -102,7 +102,7 @@ "@types/node": "^18.18.5", "@types/punycode": "^2.1.3", "@vitejs/plugin-vue": "^4.4.0", - "@vitest/ui": "^1.1.3", + "@vitest/ui": "^3.0.5", "@vue/eslint-config-typescript": "^12.0.0", "@vue/test-utils": "^2.4.2", "@vue/tsconfig": "^0.4.0", @@ -130,7 +130,7 @@ "vite": "^4.4.11", "vite-plugin-dts": "^4.5.0", "vite-plugin-svgstring": "^1.0.0", - "vitest": "^1.1.3", + "vitest": "^3.0.5", "vue-tsc": "^2.2.0" } } diff --git a/packages/component-library/src/components/form/mt-url-field/mt-url-field.interactive.stories.ts b/packages/component-library/src/components/form/mt-url-field/mt-url-field.interactive.stories.ts index 0a23ffc97..35ad18141 100644 --- a/packages/component-library/src/components/form/mt-url-field/mt-url-field.interactive.stories.ts +++ b/packages/component-library/src/components/form/mt-url-field/mt-url-field.interactive.stories.ts @@ -1,4 +1,4 @@ -import { userEvent, within } from "@storybook/test"; +import { fn, userEvent, within, expect } from "@storybook/test"; import meta, { type MtUrlFieldMeta, type MtUrlFieldStory } from "./mt-url-field.stories"; export default { @@ -18,6 +18,20 @@ export const VisualTestFocused: MtUrlFieldStory = { }, }; +export const VisualTestPlaceholder: MtUrlFieldStory = { + name: "With placeholder", + args: { + placeholder: "Placeholder", + }, +}; + +export const VisualTestRequired: MtUrlFieldStory = { + name: "Required", + args: { + required: true, + }, +}; + export const VisualTestHttps: MtUrlFieldStory = { name: "shows HTTPS mode", args: { @@ -83,3 +97,37 @@ export const VisualTestSmall: MtUrlFieldStory = { size: "small", }, }; + +export const VisualTestHelpText: MtUrlFieldStory = { + name: "Helptext", + args: { + modelValue: "https://example.com", + helpText: "This is help text", + }, +}; + +export const VisualTestHint: MtUrlFieldStory = { + name: "Hint", + args: { + hint: "This is a hint", + }, +}; + +export const VisualTestCopyToClipboard: MtUrlFieldStory = { + name: "Copy to clipboard", + args: { + modelValue: "https://www.example.com", + copyable: true, + }, + async play({ canvasElement }) { + const handler = fn(); + navigator.clipboard.writeText = handler; + + const canvas = within(canvasElement); + + await userEvent.click(canvas.getByRole("button", { name: "Copy URL to clipboard" })); + + await expect(handler).toHaveBeenCalledOnce(); + await expect(handler).toHaveBeenCalledWith("www.example.com"); + }, +}; diff --git a/packages/component-library/src/components/form/mt-url-field/mt-url-field.spec.ts b/packages/component-library/src/components/form/mt-url-field/mt-url-field.spec.ts index f7119698a..46830ff52 100644 --- a/packages/component-library/src/components/form/mt-url-field/mt-url-field.spec.ts +++ b/packages/component-library/src/components/form/mt-url-field/mt-url-field.spec.ts @@ -51,7 +51,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -74,7 +73,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "", - // @ts-expect-error -- Event is not typed, yet onChange: handler, }, }); @@ -171,7 +169,6 @@ describe("mt-url-field", async () => { props: { modelValue: "www.example.com", disabled: true, - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -179,9 +176,6 @@ describe("mt-url-field", async () => { // ACT await userEvent.type(screen.getByRole("textbox"), "www.shopware.com"); - // Is needed to emit the "onUpdate:modelValue" event - await userEvent.tab(); - // ASSERT expect(screen.getByRole("textbox")).toHaveValue("www.example.com"); expect(screen.getByRole("textbox")).toBeDisabled(); @@ -189,6 +183,25 @@ describe("mt-url-field", async () => { expect(handler).not.toHaveBeenCalled(); }); + it("does not emit an onUpdate:modelValue event when removing focus from the input", async () => { + // ARRANGE + const handler = vi.fn(); + + render(MtUrlField, { + props: { + modelValue: "www.example.com", + "onUpdate:modelValue": handler, + }, + }); + + // ACT + await userEvent.click(screen.getByRole("textbox")); + await userEvent.tab(); + + // ASSERT + expect(handler).not.toHaveBeenCalled(); + }); + it("cannot be switched to the http protocol when the field is disabled", async () => { // ARRANGE const handler = vi.fn(); @@ -197,7 +210,6 @@ describe("mt-url-field", async () => { props: { modelValue: "https://www.example.com", disabled: true, - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -218,7 +230,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "https://www.example.com", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -239,7 +250,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "http://www.example.com", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -285,7 +295,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "", - // @ts-expect-error -- Event is not typed, yet onChange: handler, }, }); @@ -305,15 +314,15 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { + label: "URL", isInheritanceField: true, isInherited: true, - // @ts-expect-error "onInheritance-remove": handler, }, }); // ACT - await userEvent.click(screen.getByTestId("mt-inheritance-switch-icon")); + await userEvent.click(screen.getByRole("button", { name: "Unlink inheritance" })); // ASSERT expect(handler).toHaveBeenCalledOnce(); @@ -325,15 +334,15 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { + label: "URL", isInheritanceField: true, isInherited: false, - // @ts-expect-error "onInheritance-restore": handler, }, }); // ACT - await userEvent.click(screen.getByTestId("mt-icon__regular-link-horizontal-slash")); + await userEvent.click(screen.getByRole("button", { name: "Link inheritance" })); // ASSERT expect(handler).toHaveBeenCalledOnce(); @@ -348,7 +357,6 @@ describe("mt-url-field", async () => { isInheritanceField: true, isInherited: true, modelValue: "www.example.com", - // @ts-expect-error "onUpdate:modelValue": handler, }, }); @@ -373,7 +381,6 @@ describe("mt-url-field", async () => { isInheritanceField: true, isInherited: false, modelValue: "", - // @ts-expect-error "onUpdate:modelValue": handler, }, }); @@ -398,7 +405,6 @@ describe("mt-url-field", async () => { isInheritanceField: true, isInherited: true, modelValue: "http://www.example.com", - // @ts-expect-error "onUpdate:modelValue": handler, }, }); @@ -421,7 +427,6 @@ describe("mt-url-field", async () => { props: { modelValue: "https://www.example.com?foo=bar", omitUrlSearch: true, - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -439,7 +444,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "https://www.example.com?foo=bar", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -458,7 +462,6 @@ describe("mt-url-field", async () => { props: { modelValue: "https://www.example.com#foo", omitUrlHash: true, - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -476,7 +479,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "https://www.example.com#foo", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -494,7 +496,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "www.example.com ", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); @@ -512,7 +513,6 @@ describe("mt-url-field", async () => { render(MtUrlField, { props: { modelValue: "localhost/h%C3%A4ndler", - // @ts-expect-error -- Event is not typed, yet "onUpdate:modelValue": handler, }, }); diff --git a/packages/component-library/src/components/form/mt-url-field/mt-url-field.stories.ts b/packages/component-library/src/components/form/mt-url-field/mt-url-field.stories.ts index d91aa4b59..0cca00b4d 100644 --- a/packages/component-library/src/components/form/mt-url-field/mt-url-field.stories.ts +++ b/packages/component-library/src/components/form/mt-url-field/mt-url-field.stories.ts @@ -1,102 +1,36 @@ import MtUrlField from "./mt-url-field.vue"; -import baseFieldArgTypes from "../_internal/mt-base-field/arg-types"; -import type { StoryObj } from "@storybook/vue3"; -import type { SlottedMeta } from "@/_internal/story-helper"; +import type { Meta, StoryObj } from "@storybook/vue3"; -export type MtUrlFieldMeta = SlottedMeta< - typeof MtUrlField, - | "default" - | "change" - | "hint" - | "label" - | "placeholder" - | "error" - | "inheritanceRestore" - | "inheritanceRemove" - | "isInherited" - | "updateModelValue" ->; +export type MtUrlFieldMeta = Meta; export default { title: "Components/Form/mt-url-field", component: MtUrlField, render: (args) => ({ components: { MtUrlField }, - template: `
- - - - - - -

hidden

-
`, - data() { - return { currentValue: "" }; - }, - watch: { - "args.modelValue"(v) { - this.currentValue = v; - }, - }, - created() { - this.currentValue = args.modelValue; - }, - methods: { - inheritanceRemoveWrapper(a: any) { - args.inheritanceRemove(...a); - args.isInherited = false; - }, + setup: () => ({ args }), + template: ` + + - inheritanceRestoreWrapper(a: any) { - args.inheritanceRestore(...a); - args.isInherited = true; - }, + - onChange(value: string) { - args.change(value); - this.currentValue = value; - }, - - onUpdateModelValue(value: string) { - args.updateModelValue(value); - this.currentValue = value; - }, - }, - setup: () => { - return { - args, - }; - }, + +`, }), args: { label: "Url field", size: "default", }, - argTypes: { - ...baseFieldArgTypes, - }, } as MtUrlFieldMeta; -export type MtUrlFieldStory = StoryObj; +export type MtUrlFieldStory = StoryObj; export const DefaultStory: MtUrlFieldStory = { name: "mt-url-field", diff --git a/packages/component-library/src/components/form/mt-url-field/mt-url-field.vue b/packages/component-library/src/components/form/mt-url-field/mt-url-field.vue index 85d8ce4ef..20083877e 100644 --- a/packages/component-library/src/components/form/mt-url-field/mt-url-field.vue +++ b/packages/component-library/src/components/form/mt-url-field/mt-url-field.vue @@ -1,231 +1,399 @@ - - diff --git a/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.spec.ts b/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.spec.ts index 753dedc3a..11c8391e8 100644 --- a/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.spec.ts +++ b/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.spec.ts @@ -27,8 +27,8 @@ describe("mt-tooltip", () => { // ACT & ASSERT expect(screen.queryByRole("tooltip", { name: "Tooltip" })).not.toBeInTheDocument(); - expect(screen.getByRole("button", { name: "Open tooltip" })).not.toHaveAttribute( - "aria-describedby", + expect(screen.getByRole("button", { name: "Open tooltip" })).toHaveAccessibleDescription( + "Tooltip", ); }); @@ -108,7 +108,7 @@ describe("mt-tooltip", () => { }, ); - it("does not announce the content of the tooltip when the tooltip is hidden", async () => { + it("does read out the tooltip content when focusing the trigger", async () => { // ARRANGE render(MtTooltip, { props: { @@ -123,10 +123,10 @@ describe("mt-tooltip", () => { await flushPromises(); // ACT & ASSERT - expect(screen.getByRole("button", { name: "Open tooltip" })).not.toHaveAccessibleDescription( + expect(screen.getByRole("button", { name: "Open tooltip" })).toHaveAccessibleDescription( "Tooltip", ); - expect(screen.getByRole("button", { name: "Open tooltip" })).not.toHaveAttribute( + expect(screen.getByRole("button", { name: "Open tooltip" })).toHaveAttribute( "aria-describedby", ); }); diff --git a/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.vue b/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.vue index 97e38741e..dc081946b 100644 --- a/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.vue +++ b/packages/component-library/src/components/overlay/mt-tooltip/mt-tooltip.vue @@ -17,7 +17,7 @@ onMouseleave, onMousedown, onMouseup: () => setState({ isPressingTrigger: false }), - 'aria-describedby': isVisible ? `mt-tooltip--${id}__tooltip` : undefined, + 'aria-describedby': `mt-tooltip--${id}__tooltip`, }" /> @@ -37,6 +37,7 @@ {{ content }}