From 5767e1124f7685b8848b83641e760c4f71ae5521 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:06:45 +0100 Subject: [PATCH 01/31] [ch-dialog] Rename `hidden` prop to `show` --- src/components/dialog/dialog.scss | 4 ++ src/components/dialog/dialog.tsx | 48 +++++++++++-------- .../components/dialog/dialog.showcase.tsx | 14 +++--- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/components/dialog/dialog.scss b/src/components/dialog/dialog.scss index 819abaa43..0e932f39d 100644 --- a/src/components/dialog/dialog.scss +++ b/src/components/dialog/dialog.scss @@ -150,6 +150,10 @@ $ch-dialog-y--same-layer: calc( display: contents; } +:host(.ch-dialog--hidden) { + display: none; +} + // - - - - - - - - - - - - - - - - // Header // - - - - - - - - - - - - - - - - diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index eb61e7d4c..2d862bdae 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -268,21 +268,21 @@ export class ChDialog { @Prop() readonly closeButtonAccessibleName?: string; /** - * Specifies whether the dialog is hidden or visible. + * Specifies whether the dialog is shown or not. */ // eslint-disable-next-line @stencil-community/ban-default-true - @Prop({ mutable: true, reflect: true }) hidden = true; - @Watch("hidden") - handleHiddenChange(hidden: boolean) { + @Prop({ mutable: true }) show = false; + @Watch("show") + handleShowChange(show: boolean) { // Schedule update for watchers this.#checkBorderSizeWatcher = true; this.#checkPositionWatcher = true; // Update the dialog visualization - if (hidden) { - this.#dialogRef.close(); - } else { + if (show) { this.#showModal(); + } else { + this.#dialogRef.close(); } } @@ -291,7 +291,7 @@ export class ChDialog { * interrupt interaction with the rest of the page being inert, while * non-modal dialog boxes allow interaction with the rest of the page. * - * Note: If `hidden !== false`, this property does not reflect changes on + * Note: If `show !== true`, this property does not reflect changes on * runtime, since at the time of writing browsers do not support switching * from modal to not-modal, (or vice-versa). */ @@ -358,7 +358,7 @@ export class ChDialog { } componentDidLoad() { - if (!this.hidden) { + if (this.show) { // Schedule update for watchers this.#checkBorderSizeWatcher = true; this.#checkPositionWatcher = true; @@ -398,7 +398,7 @@ export class ChDialog { }; #handleDialogClose = () => { - this.hidden = true; + this.show = false; // Emit events only when the action is committed by the user this.dialogClosed.emit(); document.removeEventListener("click", this.#evaluateClickOnDocument, { @@ -505,7 +505,7 @@ export class ChDialog { }; #closeHandler = () => { - this.hidden = true; + this.show = false; }; #evaluateClickOnDocument = (e: MouseEvent) => { @@ -653,7 +653,7 @@ export class ChDialog { */ // eslint-disable-next-line @stencil-community/own-props-must-be-private #setBorderSizeWatcher = () => { - if (!this.resizable || this.hidden) { + if (!this.resizable || !this.show) { this.#removeBorderSizeWatcher(); return; } @@ -734,9 +734,10 @@ export class ChDialog { return ( @@ -749,7 +750,7 @@ export class ChDialog { ref={el => (this.#dialogRef = el)} > {this.showHeader && ( -
)} -
+ )} -
+
{this.showFooter && ( - + )} {this.resizable && - !this.hidden && [ + this.show && [
, // Top
, // Right
, // Bottom
, // Left
, // Top Left
, // Top Right
, // Bottom Left
= {}; const handleClose = () => { - state.hidden = true; + state.show = false; // TODO: Until we support external slots in the ch-flexible-layout-render, // this is a hack to update the render of the widget and thus re-render the @@ -24,7 +24,7 @@ const handleClose = () => { }; const handleDialogOpen = () => { - state.hidden = false; + state.show = true; // TODO: Until we support external slots in the ch-flexible-layout-render, // this is a hack to update the render of the widget and thus re-render the @@ -43,7 +43,7 @@ const render = () => [ caption={state.caption} class="dialog dialog-primary" closeButtonAccessibleName={state.closeButtonAccessibleName} - hidden={state.hidden} + show={state.show} modal={state.modal} resizable={state.resizable} showFooter={state.showFooter} @@ -84,9 +84,9 @@ const showcaseRenderProperties: ShowcaseRenderProperties = columns: 2, properties: [ { - id: "hidden", - caption: "Hidden", - value: true, + id: "show", + caption: "Show", + value: false, type: "boolean" }, { @@ -177,7 +177,7 @@ const showcasePropertiesInfo: ShowcaseTemplatePropertyInfo Date: Thu, 2 Jan 2025 14:06:58 +0100 Subject: [PATCH 02/31] [ch-dialog] Update readme --- src/components/dialog/readme.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/dialog/readme.md b/src/components/dialog/readme.md index 0123f8b44..7db40ef55 100644 --- a/src/components/dialog/readme.md +++ b/src/components/dialog/readme.md @@ -12,17 +12,17 @@ interactive component. ## Properties -| Property | Attribute | Description | Type | Default | -| --------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----------- | -| `adjustPositionAfterResize` | `adjust-position-after-resize` | `true` if the dialog should be repositioned after resize. | `boolean` | `false` | -| `allowDrag` | `allow-drag` | "box" will allow the dialog to be draggable from both the header and the content. "header" will allow the dialog to be draggable only from the header. "no" disables dragging completely. | `"box" \| "header" \| "no"` | `"no"` | -| `caption` | `caption` | Refers to the dialog title. I will ve visible if 'showHeader´is true. | `string` | `undefined` | -| `closeButtonAccessibleName` | `close-button-accessible-name` | Specifies a short string, typically 1 to 3 words, that authors associate with an element to provide users of assistive technologies with a label for the element. This label is used for the close button of the header. | `string` | `undefined` | -| `hidden` | `hidden` | Specifies whether the dialog is hidden or visible. | `boolean` | `true` | -| `modal` | `modal` | Specifies whether the dialog is a modal or not. Modal dialog boxes interrupt interaction with the rest of the page being inert, while non-modal dialog boxes allow interaction with the rest of the page. Note: If `hidden !== false`, this property does not reflect changes on runtime, since at the time of writing browsers do not support switching from modal to not-modal, (or vice-versa). | `boolean` | `true` | -| `resizable` | `resizable` | Specifies whether the control can be resized. If `true` the control can be resized at runtime by dragging the edges or corners. | `boolean` | `false` | -| `showFooter` | `show-footer` | Specifies whether the dialog footer is hidden or visible. | `boolean` | `false` | -| `showHeader` | `show-header` | Specifies whether the dialog header is hidden or visible. | `boolean` | `false` | +| Property | Attribute | Description | Type | Default | +| --------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | ----------- | +| `adjustPositionAfterResize` | `adjust-position-after-resize` | `true` if the dialog should be repositioned after resize. | `boolean` | `false` | +| `allowDrag` | `allow-drag` | "box" will allow the dialog to be draggable from both the header and the content. "header" will allow the dialog to be draggable only from the header. "no" disables dragging completely. | `"box" \| "header" \| "no"` | `"no"` | +| `caption` | `caption` | Refers to the dialog title. I will ve visible if 'showHeader´is true. | `string` | `undefined` | +| `closeButtonAccessibleName` | `close-button-accessible-name` | Specifies a short string, typically 1 to 3 words, that authors associate with an element to provide users of assistive technologies with a label for the element. This label is used for the close button of the header. | `string` | `undefined` | +| `modal` | `modal` | Specifies whether the dialog is a modal or not. Modal dialog boxes interrupt interaction with the rest of the page being inert, while non-modal dialog boxes allow interaction with the rest of the page. Note: If `show !== true`, this property does not reflect changes on runtime, since at the time of writing browsers do not support switching from modal to not-modal, (or vice-versa). | `boolean` | `true` | +| `resizable` | `resizable` | Specifies whether the control can be resized. If `true` the control can be resized at runtime by dragging the edges or corners. | `boolean` | `false` | +| `show` | `show` | Specifies whether the dialog is shown or not. | `boolean` | `false` | +| `showFooter` | `show-footer` | Specifies whether the dialog footer is hidden or visible. | `boolean` | `false` | +| `showHeader` | `show-header` | Specifies whether the dialog header is hidden or visible. | `boolean` | `false` | ## Events From a6e318714dcd435aec4a913b6fdf90367959ba57 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:07:54 +0100 Subject: [PATCH 03/31] [ch-dialog] Include base tests Include the following base tests files: - basic - parts - slots --- src/components/dialog/tests/basic.e2e.ts | 57 ++++++++++++++++++ src/components/dialog/tests/parts.e2e.ts | 77 ++++++++++++++++++++++++ src/components/dialog/tests/slots.e2e.ts | 51 ++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/components/dialog/tests/basic.e2e.ts create mode 100644 src/components/dialog/tests/parts.e2e.ts create mode 100644 src/components/dialog/tests/slots.e2e.ts diff --git a/src/components/dialog/tests/basic.e2e.ts b/src/components/dialog/tests/basic.e2e.ts new file mode 100644 index 000000000..cee8d1f72 --- /dev/null +++ b/src/components/dialog/tests/basic.e2e.ts @@ -0,0 +1,57 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; + +describe("[ch-dialog][basic]", () => { + let page: E2EPage; + let dialogRef: E2EElement; + + // Helper functions + const testDefaultProperty = async ( + propertyName: string, + expectedValue: any + ) => { + it(`the "${propertyName}" property should be ${ + expectedValue === undefined ? "undefined" : `"${expectedValue}"` + }`, async () => { + const propertyValue = await dialogRef.getProperty(propertyName); + if (expectedValue === undefined) { + expect(propertyValue).toBeUndefined(); + } else { + expect(propertyValue).toBe(expectedValue); + } + }); + }; + + beforeEach(async () => { + page = await newE2EPage({ + html: ``, + failOnConsoleError: true + }); + dialogRef = await page.find("ch-dialog"); + }); + + // Validate shadowRoot + + it("should have a shadowRoot", async () => { + expect(dialogRef.shadowRoot).toBeTruthy(); + }); + + // Validate properties default values + + testDefaultProperty("adjustPositionAfterResize", false); + + testDefaultProperty("allowDrag", "no"); + + testDefaultProperty("caption", undefined); + + testDefaultProperty("closeButtonAccessibleName", undefined); + + testDefaultProperty("show", false); + + testDefaultProperty("modal", true); + + testDefaultProperty("resizable", false); + + testDefaultProperty("showFooter", false); + + testDefaultProperty("showHeader", false); +}); diff --git a/src/components/dialog/tests/parts.e2e.ts b/src/components/dialog/tests/parts.e2e.ts new file mode 100644 index 000000000..89f2d5a7b --- /dev/null +++ b/src/components/dialog/tests/parts.e2e.ts @@ -0,0 +1,77 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; + +describe("[ch-dialog][parts]", () => { + let page: E2EPage; + let dialogRef: E2EElement; + + const testPart = ( + selector: string, + elementDescription: string, + part: string + ) => + it(`the ${elementDescription} should have the "${part}" part`, async () => { + const elementRef = await page.find(`ch-dialog >>> ${selector}`); + expect(elementRef).not.toBeNull(); + // expect(elementRef.getAttribute("part")).toContain(part); + }); + + beforeEach(async () => { + page = await newE2EPage({ + html: ``, + failOnConsoleError: true + }); + dialogRef = await page.find("ch-dialog"); + + // Some parts depend on properties not being false or undefined: + await dialogRef.setProperty("showHeader", true); + await dialogRef.setProperty("caption", "Some Caption"); + await dialogRef.setProperty("show", true); + await dialogRef.setProperty("resizable", true); + await page.waitForChanges(); + }); + + testPart('[part="dialog"]', "dialog", "dialog"); + testPart('[part="header"]', "header", "header"); + testPart('[part="caption"]', "caption", "caption"); + testPart('[part="close-button"]', "close-button", "close-button"); + testPart('[part="content"]', "content", "content"); + + // Edges and corners + testPart( + '[part="edge edge-block-start"]', + "edge-block-start", + "edge-block-start" + ); + testPart( + '[part="edge edge-inline-end"]', + "edge-inline-end", + "edge-inline-end" + ); + testPart('[part="edge edge-block-end"]', "edge-block-end", "edge-block-end"); + testPart( + '[part="edge edge-inline-start"]', + "edge-inline-start", + "edge-inline-start" + ); + + testPart( + '[part="corner corner-block-start-inline-start"]', + "corner-block-start-inline-start", + "corner-block-start-inline-start" + ); + testPart( + '[part="corner corner-block-start-inline-end"]', + "corner-block-start-inline-end", + "corner-block-start-inline-end" + ); + testPart( + '[part="corner corner-block-end-inline-start"]', + "corner-block-end-inline-start", + "corner-block-end-inline-start" + ); + testPart( + '[part="corner corner-block-end-inline-end"]', + "corner-block-end-inline-end", + "corner-block-end-inline-end" + ); +}); diff --git a/src/components/dialog/tests/slots.e2e.ts b/src/components/dialog/tests/slots.e2e.ts new file mode 100644 index 000000000..8364f568b --- /dev/null +++ b/src/components/dialog/tests/slots.e2e.ts @@ -0,0 +1,51 @@ +import { E2EPage, newE2EPage } from "@stencil/core/testing"; + +describe("[ch-dialog][slots]", () => { + let page: E2EPage; + let dialogRef; + beforeEach(async () => { + page = await newE2EPage({ + html: ``, + failOnConsoleError: true + }); + dialogRef = await page.find("ch-dialog"); + }); + + // default slot + it("should have a slot element by default", async () => { + const slot = await page.find("ch-dialog >>> slot"); + expect(slot).not.toBeNull(); + }); + + // footer slot + it("should render a 'footer' slot if showFooter is true", async () => { + dialogRef.setProperty("showFooter", true); + await page.waitForChanges(); + const slotFooter = await page.find( + "ch-dialog >>> dialog footer slot[name='footer']" + ); + expect(slotFooter).not.toBeNull(); + }); + + it("should not render a 'footer' slot if showFooter is false", async () => { + dialogRef.setProperty("showFooter", false); + await page.waitForChanges(); + const slotFooter = await page.find( + "ch-dialog >>> dialog footer slot[name='footer']" + ); + expect(slotFooter).toBeNull(); + }); + + it("should render multiple elements in the default slot", async () => { + await page.setContent(` + +
First Content
+
Second Content
+
+ `); + const contents = await page.findAll("ch-dialog .default-slot-content"); + expect(contents.length).toBe(2); + expect(contents[0].textContent).toBe("First Content"); + expect(contents[1].textContent).toBe("Second Content"); + }); +}); From 10b631552eeae946093d725bc2d63daf1298827f Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:10:04 +0100 Subject: [PATCH 04/31] [ch-dialog] Include behavior tests files add the following tests files: - drag - modal - position --- src/components/dialog/tests/_utils.ts | 33 ++ .../dialog/tests/behaviors/drag.e2e.ts | 460 ++++++++++++++++++ .../dialog/tests/behaviors/modal.e2e.ts | 67 +++ .../dialog/tests/behaviors/position.e2e.ts | 78 +++ 4 files changed, 638 insertions(+) create mode 100644 src/components/dialog/tests/_utils.ts create mode 100644 src/components/dialog/tests/behaviors/drag.e2e.ts create mode 100644 src/components/dialog/tests/behaviors/modal.e2e.ts create mode 100644 src/components/dialog/tests/behaviors/position.e2e.ts diff --git a/src/components/dialog/tests/_utils.ts b/src/components/dialog/tests/_utils.ts new file mode 100644 index 000000000..c515fb8c5 --- /dev/null +++ b/src/components/dialog/tests/_utils.ts @@ -0,0 +1,33 @@ +// utils/_utils.ts + +import { E2EPage } from "@stencil/core/testing"; + +export interface CustomDOMRect { + x: number; + y: number; + width: number; + height: number; +} + +export async function getDialogPartRect( + page: E2EPage, + partSelector: string = null +): Promise { + return await page.evaluate(partSelector => { + const chDialog = document.querySelector("ch-dialog"); + if (!chDialog) { + throw new Error("ch-dialog element not found."); + } + + const rect = chDialog.shadowRoot + .querySelector(partSelector) + .getBoundingClientRect(); + + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + }; + }, partSelector); +} diff --git a/src/components/dialog/tests/behaviors/drag.e2e.ts b/src/components/dialog/tests/behaviors/drag.e2e.ts new file mode 100644 index 000000000..0aed535f4 --- /dev/null +++ b/src/components/dialog/tests/behaviors/drag.e2e.ts @@ -0,0 +1,460 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; +import { getDialogPartRect, CustomDOMRect } from "../_utils"; + +// Note: Some issues have been experienced after doing more than one drag +// on a single "it" test. To avoid this issues, dragging tests have been +// done in separate "it" tests. + +const viewPortWidth = 1920; +const viewPortHeight = 1080; + +describe("[ch-dialog][allowDrag]", () => { + let page: E2EPage; + let chDialogRef: E2EElement; + let headerPart: E2EElement; + let contentPart: E2EElement; + let footerPart: E2EElement; + const dialogSelector = "dialog"; + const headerSelector = "[part='header']"; + const contentSelector = "[part='content']"; + const footerSelector = "[part='footer']"; + const chDialogBlockStart = 50; + const chDialogInlineStart = 50; + const threshold = 0; + const dragBlockValue = 10; + const dragInlineValue = 10; + + beforeEach(async () => { + page = await newE2EPage({ + html: ` + + Dialog Content +
+ +
+
`, + failOnConsoleError: true + }); + await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); + chDialogRef = await page.find("ch-dialog"); + + chDialogRef.setProperty("caption", "Caption that appears on the header"); + await page.waitForChanges(); + chDialogRef.setProperty("showHeader", true); + await page.waitForChanges(); + chDialogRef.setProperty("showFooter", true); + await page.waitForChanges(); + + headerPart = await page.find(`ch-dialog >>> ${headerSelector}`); + contentPart = await page.find(`ch-dialog >>> ${contentSelector}`); + footerPart = await page.find(`ch-dialog >>> ${footerSelector}`); + + if (!headerPart || !contentPart || !footerPart) { + throw new Error("header part of footer part not found."); + } + + // Set the dialog x and y on 0, to make it easier to evaluate drag changes. + await page.evaluate( + (x, y) => { + const style = document.createElement("style"); + style.textContent = ` + ch-dialog { + --ch-dialog-inline-start: ${x}px; + --ch-dialog-block-start: ${y}px; + } + `; + document.head.appendChild(style); + }, + chDialogInlineStart, + chDialogBlockStart + ); + }); + + // Drag from header + + it("should allow drag from the header only", async () => { + chDialogRef.setProperty("allowDrag", "header"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDrag: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const headerRectBeforeDrag: CustomDOMRect = await getDialogPartRect( + page, + headerSelector + ); + + expect(dialogRectBeforeDrag.x).toBeCloseTo(chDialogInlineStart, threshold); + expect(dialogRectBeforeDrag.y).toBeCloseTo(chDialogBlockStart, threshold); + + // then drag + await page.mouse.move(headerRectBeforeDrag.x, headerRectBeforeDrag.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDrag: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterDrag.x).toBeCloseTo(dragInlineValue, threshold); + expect(dialogRectAfterDrag.y).toBeCloseTo(dragBlockValue, threshold); + }); + + it("should not allow drag from the content (header only)", async () => { + chDialogRef.setProperty("allowDrag", "header"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const contentRect: CustomDOMRect = await getDialogPartRect( + page, + contentSelector + ); + + expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + // then try to drag from the content + await page.mouse.move(contentRect.x, contentRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from content + expect(dialogRectAfterDragFromContent.x).toBeCloseTo( + dialogRectBeforeDragFromContent.x, + threshold + ); + expect(dialogRectAfterDragFromContent.y).toBeCloseTo( + dialogRectBeforeDragFromContent.y, + threshold + ); + }); + + it("should not allow drag from the footer (header only)", async () => { + chDialogRef.setProperty("allowDrag", "header"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDragFromFooter: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const footerRect: CustomDOMRect = await getDialogPartRect( + page, + footerSelector + ); + + expect(dialogRectBeforeDragFromFooter.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromFooter.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + // then try to drag from the footer + await page.mouse.move(footerRect.x, footerRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromFooter: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from content + expect(dialogRectAfterDragFromFooter.x).toBeCloseTo( + dialogRectBeforeDragFromFooter.x, + threshold + ); + expect(dialogRectAfterDragFromFooter.y).toBeCloseTo( + dialogRectBeforeDragFromFooter.y, + threshold + ); + }); + + // Drag from footer + + it("should allow drag from the footer only", async () => { + chDialogRef.setProperty("allowDrag", "footer"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDrag: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const footerRectBeforeDrag: CustomDOMRect = await getDialogPartRect( + page, + footerSelector + ); + + expect(dialogRectBeforeDrag.x).toBeCloseTo(chDialogInlineStart, threshold); + expect(dialogRectBeforeDrag.y).toBeCloseTo(chDialogBlockStart, threshold); + + // then drag + await page.mouse.move(footerRectBeforeDrag.x, footerRectBeforeDrag.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDrag: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterDrag.x).toBeCloseTo( + dialogRectBeforeDrag.x, + threshold + ); + expect(dialogRectAfterDrag.y).toBeCloseTo( + dialogRectBeforeDrag.y, + threshold + ); + }); + + it("should not allow drag from the content (footer only)", async () => { + chDialogRef.setProperty("allowDrag", "footer"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const contentRect: CustomDOMRect = await getDialogPartRect( + page, + contentSelector + ); + + expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + // then try to drag from the content + await page.mouse.move(contentRect.x, contentRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from content + expect(dialogRectAfterDragFromContent.x).toBeCloseTo( + dialogRectBeforeDragFromContent.x, + threshold + ); + expect(dialogRectAfterDragFromContent.y).toBeCloseTo( + dialogRectBeforeDragFromContent.y, + threshold + ); + }); + + it("should not allow drag from the header (footer only)", async () => { + chDialogRef.setProperty("allowDrag", "footer"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // get parts coordinates + const dialogRectBeforeDragFromHeader: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const headerRect: CustomDOMRect = await getDialogPartRect( + page, + headerSelector + ); + + expect(dialogRectBeforeDragFromHeader.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromHeader.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + // then try to drag from the header + await page.mouse.move(headerRect.x, headerRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromHeader: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from header + expect(dialogRectAfterDragFromHeader.x).toBeCloseTo( + dialogRectBeforeDragFromHeader.x, + threshold + ); + expect(dialogRectAfterDragFromHeader.y).toBeCloseTo( + dialogRectBeforeDragFromHeader.y, + threshold + ); + }); + + // Do not drag + + it("should not allow drag from the header", async () => { + chDialogRef.setProperty("allowDrag", "no"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + const dialogRectBeforeDragFromHeader: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const headerRect: CustomDOMRect = await getDialogPartRect( + page, + headerSelector + ); + + expect(dialogRectBeforeDragFromHeader.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromHeader.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + await page.mouse.move(headerRect.x, headerRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromHeader: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from header + expect(dialogRectAfterDragFromHeader.x).toBeCloseTo( + dialogRectBeforeDragFromHeader.x, + threshold + ); + expect(dialogRectAfterDragFromHeader.y).toBeCloseTo( + dialogRectBeforeDragFromHeader.y, + threshold + ); + }); + + it("should not allow drag from the content", async () => { + chDialogRef.setProperty("allowDrag", "no"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + const dialogRectBeforeDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const contentRect: CustomDOMRect = await getDialogPartRect( + page, + contentSelector + ); + + expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + await page.mouse.move(contentRect.x, contentRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromContent: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from content + expect(dialogRectAfterDragFromContent.x).toBeCloseTo( + dialogRectBeforeDragFromContent.x, + threshold + ); + expect(dialogRectAfterDragFromContent.y).toBeCloseTo( + dialogRectBeforeDragFromContent.y, + threshold + ); + }); + + it("should not allow drag from the footer", async () => { + chDialogRef.setProperty("allowDrag", "no"); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + // - - - - - - - - - - - - - + // Try to drag from footer + // - - - - - - - - - - - - - + + const dialogRectBeforeDragFromFooter: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + const footerRect: CustomDOMRect = await getDialogPartRect( + page, + footerSelector + ); + + expect(dialogRectBeforeDragFromFooter.x).toBeCloseTo( + chDialogInlineStart, + threshold + ); + expect(dialogRectBeforeDragFromFooter.y).toBeCloseTo( + chDialogBlockStart, + threshold + ); + + await page.mouse.move(footerRect.x, footerRect.y); + await page.mouse.down(); + await page.mouse.move(dragInlineValue, dragBlockValue); + await page.mouse.up(); + + const dialogRectAfterDragFromFooter: CustomDOMRect = + await getDialogPartRect(page, dialogSelector); + + // Dialog should not have been moved after drag from footer + expect(dialogRectAfterDragFromFooter.x).toBeCloseTo( + dialogRectBeforeDragFromFooter.x, + threshold + ); + expect(dialogRectAfterDragFromFooter.y).toBeCloseTo( + dialogRectBeforeDragFromFooter.y, + threshold + ); + }); +}); diff --git a/src/components/dialog/tests/behaviors/modal.e2e.ts b/src/components/dialog/tests/behaviors/modal.e2e.ts new file mode 100644 index 000000000..5b15b2fdc --- /dev/null +++ b/src/components/dialog/tests/behaviors/modal.e2e.ts @@ -0,0 +1,67 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; + +describe("[ch-dialog][modal]", () => { + let page: E2EPage; + let chDialogRef: E2EElement; + + beforeEach(async () => { + // Initialize the page with a button + page = await newE2EPage({ + html: `Dialog Content`, + failOnConsoleError: true + }); + await page.setViewport({ width: 1920, height: 1080 }); + // Apply generous viewport dimensions to ensure that a mouse click + // effectively is triggered on the body. + + chDialogRef = await page.find("ch-dialog"); + + // Attach click listener to document.body in browser context + await page.evaluate(() => { + document.documentElement.style.blockSize = "100%"; + document.body.style.blockSize = "100%"; + document.body.style.margin = "0"; + + const windowRef = window as any; + windowRef.bodyClickCount = 0; + document.body.addEventListener("click", () => { + windowRef.bodyClickCount += 1; + }); + }); + await page.waitForChanges(); + }); + + it("should not block body clicks when dialog is non-modal", async () => { + chDialogRef.setProperty("modal", false); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + await page.mouse.click(10, 10); + await page.waitForChanges(); + + const clickCount = await page.evaluate(() => { + return (window as any).bodyClickCount; + }); + + expect(clickCount).toBe(1); + }); + + it("should block body clicks when dialog is non-modal", async () => { + chDialogRef.setProperty("modal", true); + await page.waitForChanges(); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + await page.mouse.click(10, 10); + await page.waitForChanges(); + + const clickCount = await page.evaluate(() => { + return (window as any).bodyClickCount; + }); + + expect(clickCount).toBe(0); + }); +}); diff --git a/src/components/dialog/tests/behaviors/position.e2e.ts b/src/components/dialog/tests/behaviors/position.e2e.ts new file mode 100644 index 000000000..fe25735e5 --- /dev/null +++ b/src/components/dialog/tests/behaviors/position.e2e.ts @@ -0,0 +1,78 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; +import { getDialogPartRect, CustomDOMRect } from "../_utils"; + +const viewPortWidth = 1920; +const viewPortHeight = 1080; +let dialogDOMRect: CustomDOMRect; + +describe("[ch-dialog][modal]", () => { + let page: E2EPage; + let chDialogRef: E2EElement; + + beforeEach(async () => { + page = await newE2EPage({ + html: `Dialog Content`, + failOnConsoleError: true + }); + await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); + + chDialogRef = await page.find("ch-dialog"); + }); + + it.skip("should be centered on the viewport", async () => { + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + dialogDOMRect = await getDialogPartRect(page); + + const estimatedXPosition = viewPortWidth / 2 - dialogDOMRect.width / 2; + const estimatedYPosition = viewPortHeight / 2 - dialogDOMRect.height / 2; + const threshold = 0; // 0 means compare integers, not decimal places. + + expect(dialogDOMRect.x).toBeCloseTo(estimatedXPosition, threshold); + expect(dialogDOMRect.y).toBeCloseTo(estimatedYPosition, threshold); + }); + + it.skip("should consider '--ch-dialog-block-start' and '--ch-dialog-inline-start' in the initial position", async () => { + const chDialogBlockStart = 50; + const chDialogInlineStart = 50; + const threshold = 0; // 0 means compare integers, not decimal places. + + await page.evaluate( + (x, y) => { + const style = document.createElement("style"); + style.textContent = ` + ch-dialog { + --ch-dialog-inline-start: ${x}px; + --ch-dialog-block-start: ${y}px; + } + `; + document.head.appendChild(style); + }, + chDialogInlineStart, + chDialogBlockStart + ); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + dialogDOMRect = await getDialogPartRect(page); + + expect(dialogDOMRect.x).toBeCloseTo(chDialogBlockStart, threshold); + expect(dialogDOMRect.y).toBeCloseTo(chDialogInlineStart, threshold); + }); + + it.skip("should mantain position after close", async () => { + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + + dialogDOMRect = await getDialogPartRect(page); + + const estimatedXPosition = viewPortWidth / 2 - dialogDOMRect.width / 2; + const estimatedYPosition = viewPortHeight / 2 - dialogDOMRect.height / 2; + const threshold = 0; // 0 means compare integers, not decimal places. + + expect(dialogDOMRect.x).toBeCloseTo(estimatedXPosition, threshold); + expect(dialogDOMRect.y).toBeCloseTo(estimatedYPosition, threshold); + }); +}); From 66a3979faa8e66d2015406d09cc78c406c22a4e6 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:40:49 +0100 Subject: [PATCH 05/31] [ch-dialog] Update `getDialogPartRect` helper function Improve error log --- src/components/dialog/tests/_utils.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/dialog/tests/_utils.ts b/src/components/dialog/tests/_utils.ts index c515fb8c5..7a30d11e9 100644 --- a/src/components/dialog/tests/_utils.ts +++ b/src/components/dialog/tests/_utils.ts @@ -11,17 +11,26 @@ export interface CustomDOMRect { export async function getDialogPartRect( page: E2EPage, - partSelector: string = null -): Promise { + partSelector: string +): Promise { + if (!partSelector) { + throw new Error("No partSelector provided."); + } + return await page.evaluate(partSelector => { const chDialog = document.querySelector("ch-dialog"); if (!chDialog) { throw new Error("ch-dialog element not found."); } - const rect = chDialog.shadowRoot - .querySelector(partSelector) - .getBoundingClientRect(); + const element = chDialog.shadowRoot.querySelector(partSelector); + if (!element) { + throw new Error( + `Element with selector "${partSelector}" not found in ch-dialog's shadow DOM.` + ); + } + + const rect = element.getBoundingClientRect(); return { x: rect.left, From 8dead16c1f34ee882820d15ca5e5b25ed8a54706 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:41:16 +0100 Subject: [PATCH 06/31] [ch-dialog] Include "resize" behavior test (WIP) --- .../dialog/tests/behaviors/resize.e2e.ts | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 src/components/dialog/tests/behaviors/resize.e2e.ts diff --git a/src/components/dialog/tests/behaviors/resize.e2e.ts b/src/components/dialog/tests/behaviors/resize.e2e.ts new file mode 100644 index 000000000..03dedbe5a --- /dev/null +++ b/src/components/dialog/tests/behaviors/resize.e2e.ts @@ -0,0 +1,332 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; +import { getDialogPartRect, CustomDOMRect } from "../_utils"; + +// Note: Some issues have been experienced after doing more than one drag +// on a single "it" test. To avoid this issues, dragging tests have been +// done in separate "it" tests. + +const viewPortWidth = 1920; +const viewPortHeight = 1080; + +describe("[ch-dialog][resize]", () => { + let page: E2EPage; + let chDialogRef: E2EElement; + + const dialogSelector = "dialog"; + + // Corners + const cornerBlockStartInlineStartSelector = + "[part~='corner-block-start-inline-start']"; + const cornerBlockStartInlineEndSelector = + "[part~='corner-block-start-inline-end']"; + const cornerBlockEndInlineStartSelector = + "[part~='corner-block-end-inline-start']"; + const cornerBlockEndInlineEndSelector = + "[part~='corner-block-end-inline-end']"; + + // Edges + const edgeBlockEndSelector = "[part~='edge-block-end']"; + const edgeBlockStartSelector = "[part~='edge-block-start']"; + const edgeInlineEndSelector = "[part~='edge-inline-end']"; + const edgeInlineStartSelector = "[part~='edge-inline-start']"; + const chDialogBlockStart = 100; + const chDialogInlineStart = 100; + const threshold = 0; + const resizeBlockValue = 20; + const resizeInlineValue = 20; + + beforeEach(async () => { + page = await newE2EPage({ + html: ` + + Dialog Content + `, + failOnConsoleError: true + }); + await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); + chDialogRef = await page.find("ch-dialog"); + + chDialogRef.setProperty("resizable", true); + await page.waitForChanges(); + + // Set the dialog x and y on 0, to make it easier to evaluate drag changes. + await page.evaluate( + (x, y) => { + const style = document.createElement("style"); + style.textContent = ` + ch-dialog { + --ch-dialog-inline-start: ${x}px; + --ch-dialog-block-start: ${y}px; + } + `; + document.head.appendChild(style); + }, + chDialogInlineStart, + chDialogBlockStart + ); + + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + }); + + // Inline Edges + + it.skip("should resize by dragging from 'edge-inline-start' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const edgeInlineStartRect: CustomDOMRect = await getDialogPartRect( + page, + edgeInlineStartSelector + ); + + const resizeTo = edgeInlineStartRect.x - resizeInlineValue; + // resize + await page.mouse.move(edgeInlineStartRect.x, edgeInlineStartRect.y); + await page.mouse.down(); + await page.mouse.move(resizeTo, 0); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.width).toBeCloseTo( + dialogRectBeforeResize.width + resizeInlineValue, + threshold + ); + }); + + it.skip("should resize by dragging from 'edge-inline-end' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const edgeInlineEndRect: CustomDOMRect = await getDialogPartRect( + page, + edgeInlineEndSelector + ); + + const resizeTo = edgeInlineEndRect.x + resizeInlineValue; + // resize + await page.mouse.move(edgeInlineEndRect.x, edgeInlineEndRect.y); + await page.mouse.down(); + await page.mouse.move(resizeTo, edgeInlineEndRect.y); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.width).toBeCloseTo( + dialogRectBeforeResize.width + resizeInlineValue, + threshold + ); + }); + + // Block Edges + + it.skip("should resize by dragging from 'edge-block-start' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const edgeBlockStartRect: CustomDOMRect = await getDialogPartRect( + page, + edgeBlockStartSelector + ); + + const resizeTo = edgeBlockStartRect.y - resizeBlockValue; + + // resize + await page.mouse.move(edgeBlockStartRect.x, edgeBlockStartRect.y); + await page.mouse.down(); + await page.mouse.move(edgeBlockStartRect.x, resizeTo); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); + + it.skip("should resize by dragging from 'edge-block-end' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const edgeBlockEndRect: CustomDOMRect = await getDialogPartRect( + page, + edgeBlockEndSelector + ); + + const resizeTo = edgeBlockEndRect.y + resizeBlockValue; + + // resize + await page.mouse.move(edgeBlockEndRect.x, edgeBlockEndRect.y); + await page.mouse.down(); + await page.mouse.move(edgeBlockEndRect.x, resizeTo); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); + + // Corner "top-left" + + it.skip("should resize by dragging from 'corner-block-start-inline-start' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const cornerBlockStartInlineStartRect: CustomDOMRect = + await getDialogPartRect(page, cornerBlockStartInlineStartSelector); + + const resizeToX = cornerBlockStartInlineStartRect.x - resizeBlockValue; + const resizeToY = cornerBlockStartInlineStartRect.y - resizeInlineValue; + + // resize + await page.mouse.move( + cornerBlockStartInlineStartRect.x, + cornerBlockStartInlineStartRect.y + ); + await page.mouse.down(); + await page.mouse.move(resizeToX, resizeToY); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); + + // Corner "top-right" + + it.skip("should resize by dragging from 'corner-block-start-inline-end' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const cornerBlockStartInlineEndRect: CustomDOMRect = + await getDialogPartRect(page, cornerBlockStartInlineEndSelector); + + const resizeToX = cornerBlockStartInlineEndRect.x + resizeBlockValue; + const resizeToY = cornerBlockStartInlineEndRect.y - resizeInlineValue; + + // resize + await page.mouse.move( + cornerBlockStartInlineEndRect.x, + cornerBlockStartInlineEndRect.y + ); + await page.mouse.down(); + await page.mouse.move(resizeToX, resizeToY); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); + + // Corner "bottom-left" + + it.skip("should resize by dragging from 'corner-block-end-inline-start' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + const cornerBlockEndInlineStartRect: CustomDOMRect = + await getDialogPartRect(page, cornerBlockEndInlineStartSelector); + + const resizeToX = cornerBlockEndInlineStartRect.x - resizeBlockValue; + const resizeToY = cornerBlockEndInlineStartRect.y + resizeInlineValue; + + // resize + await page.mouse.move( + cornerBlockEndInlineStartRect.x, + cornerBlockEndInlineStartRect.y + ); + await page.mouse.down(); + await page.mouse.move(resizeToX, resizeToY); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); + + // Corner "bottom-right" + + it("should resize by dragging from 'corner-block-end-inline-end' part", async () => { + // get parts coordinates + const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + const cornerBlockEndInlineEndRect: CustomDOMRect = await getDialogPartRect( + page, + cornerBlockEndInlineEndSelector + ); + + const resizeToX = cornerBlockEndInlineEndRect.x + resizeBlockValue; + const resizeToY = cornerBlockEndInlineEndRect.y + resizeInlineValue; + + // resize + await page.mouse.move( + cornerBlockEndInlineEndRect.x, + cornerBlockEndInlineEndRect.y + ); + await page.mouse.down(); + await page.mouse.move(resizeToX, resizeToY, { steps: 10 }); + await page.mouse.up(); + + const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( + page, + dialogSelector + ); + + expect(dialogRectAfterResize.height).toBeCloseTo( + dialogRectBeforeResize.height + resizeBlockValue, + threshold + ); + }); +}); From 81d84dab91625f0916e75c697d54a089672a2fd3 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:48:05 +0100 Subject: [PATCH 07/31] [ch-dialog] Remove tests Tests will be added on another PR --- src/components/dialog/tests/_utils.ts | 42 -- src/components/dialog/tests/basic.e2e.ts | 57 --- .../dialog/tests/behaviors/drag.e2e.ts | 460 ------------------ .../dialog/tests/behaviors/modal.e2e.ts | 67 --- .../dialog/tests/behaviors/position.e2e.ts | 78 --- .../dialog/tests/behaviors/resize.e2e.ts | 332 ------------- src/components/dialog/tests/parts.e2e.ts | 77 --- src/components/dialog/tests/slots.e2e.ts | 51 -- 8 files changed, 1164 deletions(-) delete mode 100644 src/components/dialog/tests/_utils.ts delete mode 100644 src/components/dialog/tests/basic.e2e.ts delete mode 100644 src/components/dialog/tests/behaviors/drag.e2e.ts delete mode 100644 src/components/dialog/tests/behaviors/modal.e2e.ts delete mode 100644 src/components/dialog/tests/behaviors/position.e2e.ts delete mode 100644 src/components/dialog/tests/behaviors/resize.e2e.ts delete mode 100644 src/components/dialog/tests/parts.e2e.ts delete mode 100644 src/components/dialog/tests/slots.e2e.ts diff --git a/src/components/dialog/tests/_utils.ts b/src/components/dialog/tests/_utils.ts deleted file mode 100644 index 7a30d11e9..000000000 --- a/src/components/dialog/tests/_utils.ts +++ /dev/null @@ -1,42 +0,0 @@ -// utils/_utils.ts - -import { E2EPage } from "@stencil/core/testing"; - -export interface CustomDOMRect { - x: number; - y: number; - width: number; - height: number; -} - -export async function getDialogPartRect( - page: E2EPage, - partSelector: string -): Promise { - if (!partSelector) { - throw new Error("No partSelector provided."); - } - - return await page.evaluate(partSelector => { - const chDialog = document.querySelector("ch-dialog"); - if (!chDialog) { - throw new Error("ch-dialog element not found."); - } - - const element = chDialog.shadowRoot.querySelector(partSelector); - if (!element) { - throw new Error( - `Element with selector "${partSelector}" not found in ch-dialog's shadow DOM.` - ); - } - - const rect = element.getBoundingClientRect(); - - return { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height - }; - }, partSelector); -} diff --git a/src/components/dialog/tests/basic.e2e.ts b/src/components/dialog/tests/basic.e2e.ts deleted file mode 100644 index cee8d1f72..000000000 --- a/src/components/dialog/tests/basic.e2e.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; - -describe("[ch-dialog][basic]", () => { - let page: E2EPage; - let dialogRef: E2EElement; - - // Helper functions - const testDefaultProperty = async ( - propertyName: string, - expectedValue: any - ) => { - it(`the "${propertyName}" property should be ${ - expectedValue === undefined ? "undefined" : `"${expectedValue}"` - }`, async () => { - const propertyValue = await dialogRef.getProperty(propertyName); - if (expectedValue === undefined) { - expect(propertyValue).toBeUndefined(); - } else { - expect(propertyValue).toBe(expectedValue); - } - }); - }; - - beforeEach(async () => { - page = await newE2EPage({ - html: ``, - failOnConsoleError: true - }); - dialogRef = await page.find("ch-dialog"); - }); - - // Validate shadowRoot - - it("should have a shadowRoot", async () => { - expect(dialogRef.shadowRoot).toBeTruthy(); - }); - - // Validate properties default values - - testDefaultProperty("adjustPositionAfterResize", false); - - testDefaultProperty("allowDrag", "no"); - - testDefaultProperty("caption", undefined); - - testDefaultProperty("closeButtonAccessibleName", undefined); - - testDefaultProperty("show", false); - - testDefaultProperty("modal", true); - - testDefaultProperty("resizable", false); - - testDefaultProperty("showFooter", false); - - testDefaultProperty("showHeader", false); -}); diff --git a/src/components/dialog/tests/behaviors/drag.e2e.ts b/src/components/dialog/tests/behaviors/drag.e2e.ts deleted file mode 100644 index 0aed535f4..000000000 --- a/src/components/dialog/tests/behaviors/drag.e2e.ts +++ /dev/null @@ -1,460 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; -import { getDialogPartRect, CustomDOMRect } from "../_utils"; - -// Note: Some issues have been experienced after doing more than one drag -// on a single "it" test. To avoid this issues, dragging tests have been -// done in separate "it" tests. - -const viewPortWidth = 1920; -const viewPortHeight = 1080; - -describe("[ch-dialog][allowDrag]", () => { - let page: E2EPage; - let chDialogRef: E2EElement; - let headerPart: E2EElement; - let contentPart: E2EElement; - let footerPart: E2EElement; - const dialogSelector = "dialog"; - const headerSelector = "[part='header']"; - const contentSelector = "[part='content']"; - const footerSelector = "[part='footer']"; - const chDialogBlockStart = 50; - const chDialogInlineStart = 50; - const threshold = 0; - const dragBlockValue = 10; - const dragInlineValue = 10; - - beforeEach(async () => { - page = await newE2EPage({ - html: ` - - Dialog Content -
- -
-
`, - failOnConsoleError: true - }); - await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); - chDialogRef = await page.find("ch-dialog"); - - chDialogRef.setProperty("caption", "Caption that appears on the header"); - await page.waitForChanges(); - chDialogRef.setProperty("showHeader", true); - await page.waitForChanges(); - chDialogRef.setProperty("showFooter", true); - await page.waitForChanges(); - - headerPart = await page.find(`ch-dialog >>> ${headerSelector}`); - contentPart = await page.find(`ch-dialog >>> ${contentSelector}`); - footerPart = await page.find(`ch-dialog >>> ${footerSelector}`); - - if (!headerPart || !contentPart || !footerPart) { - throw new Error("header part of footer part not found."); - } - - // Set the dialog x and y on 0, to make it easier to evaluate drag changes. - await page.evaluate( - (x, y) => { - const style = document.createElement("style"); - style.textContent = ` - ch-dialog { - --ch-dialog-inline-start: ${x}px; - --ch-dialog-block-start: ${y}px; - } - `; - document.head.appendChild(style); - }, - chDialogInlineStart, - chDialogBlockStart - ); - }); - - // Drag from header - - it("should allow drag from the header only", async () => { - chDialogRef.setProperty("allowDrag", "header"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDrag: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const headerRectBeforeDrag: CustomDOMRect = await getDialogPartRect( - page, - headerSelector - ); - - expect(dialogRectBeforeDrag.x).toBeCloseTo(chDialogInlineStart, threshold); - expect(dialogRectBeforeDrag.y).toBeCloseTo(chDialogBlockStart, threshold); - - // then drag - await page.mouse.move(headerRectBeforeDrag.x, headerRectBeforeDrag.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDrag: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterDrag.x).toBeCloseTo(dragInlineValue, threshold); - expect(dialogRectAfterDrag.y).toBeCloseTo(dragBlockValue, threshold); - }); - - it("should not allow drag from the content (header only)", async () => { - chDialogRef.setProperty("allowDrag", "header"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const contentRect: CustomDOMRect = await getDialogPartRect( - page, - contentSelector - ); - - expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - // then try to drag from the content - await page.mouse.move(contentRect.x, contentRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from content - expect(dialogRectAfterDragFromContent.x).toBeCloseTo( - dialogRectBeforeDragFromContent.x, - threshold - ); - expect(dialogRectAfterDragFromContent.y).toBeCloseTo( - dialogRectBeforeDragFromContent.y, - threshold - ); - }); - - it("should not allow drag from the footer (header only)", async () => { - chDialogRef.setProperty("allowDrag", "header"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDragFromFooter: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const footerRect: CustomDOMRect = await getDialogPartRect( - page, - footerSelector - ); - - expect(dialogRectBeforeDragFromFooter.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromFooter.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - // then try to drag from the footer - await page.mouse.move(footerRect.x, footerRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromFooter: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from content - expect(dialogRectAfterDragFromFooter.x).toBeCloseTo( - dialogRectBeforeDragFromFooter.x, - threshold - ); - expect(dialogRectAfterDragFromFooter.y).toBeCloseTo( - dialogRectBeforeDragFromFooter.y, - threshold - ); - }); - - // Drag from footer - - it("should allow drag from the footer only", async () => { - chDialogRef.setProperty("allowDrag", "footer"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDrag: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const footerRectBeforeDrag: CustomDOMRect = await getDialogPartRect( - page, - footerSelector - ); - - expect(dialogRectBeforeDrag.x).toBeCloseTo(chDialogInlineStart, threshold); - expect(dialogRectBeforeDrag.y).toBeCloseTo(chDialogBlockStart, threshold); - - // then drag - await page.mouse.move(footerRectBeforeDrag.x, footerRectBeforeDrag.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDrag: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterDrag.x).toBeCloseTo( - dialogRectBeforeDrag.x, - threshold - ); - expect(dialogRectAfterDrag.y).toBeCloseTo( - dialogRectBeforeDrag.y, - threshold - ); - }); - - it("should not allow drag from the content (footer only)", async () => { - chDialogRef.setProperty("allowDrag", "footer"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const contentRect: CustomDOMRect = await getDialogPartRect( - page, - contentSelector - ); - - expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - // then try to drag from the content - await page.mouse.move(contentRect.x, contentRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from content - expect(dialogRectAfterDragFromContent.x).toBeCloseTo( - dialogRectBeforeDragFromContent.x, - threshold - ); - expect(dialogRectAfterDragFromContent.y).toBeCloseTo( - dialogRectBeforeDragFromContent.y, - threshold - ); - }); - - it("should not allow drag from the header (footer only)", async () => { - chDialogRef.setProperty("allowDrag", "footer"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // get parts coordinates - const dialogRectBeforeDragFromHeader: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const headerRect: CustomDOMRect = await getDialogPartRect( - page, - headerSelector - ); - - expect(dialogRectBeforeDragFromHeader.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromHeader.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - // then try to drag from the header - await page.mouse.move(headerRect.x, headerRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromHeader: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from header - expect(dialogRectAfterDragFromHeader.x).toBeCloseTo( - dialogRectBeforeDragFromHeader.x, - threshold - ); - expect(dialogRectAfterDragFromHeader.y).toBeCloseTo( - dialogRectBeforeDragFromHeader.y, - threshold - ); - }); - - // Do not drag - - it("should not allow drag from the header", async () => { - chDialogRef.setProperty("allowDrag", "no"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - const dialogRectBeforeDragFromHeader: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const headerRect: CustomDOMRect = await getDialogPartRect( - page, - headerSelector - ); - - expect(dialogRectBeforeDragFromHeader.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromHeader.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - await page.mouse.move(headerRect.x, headerRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromHeader: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from header - expect(dialogRectAfterDragFromHeader.x).toBeCloseTo( - dialogRectBeforeDragFromHeader.x, - threshold - ); - expect(dialogRectAfterDragFromHeader.y).toBeCloseTo( - dialogRectBeforeDragFromHeader.y, - threshold - ); - }); - - it("should not allow drag from the content", async () => { - chDialogRef.setProperty("allowDrag", "no"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - const dialogRectBeforeDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const contentRect: CustomDOMRect = await getDialogPartRect( - page, - contentSelector - ); - - expect(dialogRectBeforeDragFromContent.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromContent.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - await page.mouse.move(contentRect.x, contentRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromContent: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from content - expect(dialogRectAfterDragFromContent.x).toBeCloseTo( - dialogRectBeforeDragFromContent.x, - threshold - ); - expect(dialogRectAfterDragFromContent.y).toBeCloseTo( - dialogRectBeforeDragFromContent.y, - threshold - ); - }); - - it("should not allow drag from the footer", async () => { - chDialogRef.setProperty("allowDrag", "no"); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - // - - - - - - - - - - - - - - // Try to drag from footer - // - - - - - - - - - - - - - - - const dialogRectBeforeDragFromFooter: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - const footerRect: CustomDOMRect = await getDialogPartRect( - page, - footerSelector - ); - - expect(dialogRectBeforeDragFromFooter.x).toBeCloseTo( - chDialogInlineStart, - threshold - ); - expect(dialogRectBeforeDragFromFooter.y).toBeCloseTo( - chDialogBlockStart, - threshold - ); - - await page.mouse.move(footerRect.x, footerRect.y); - await page.mouse.down(); - await page.mouse.move(dragInlineValue, dragBlockValue); - await page.mouse.up(); - - const dialogRectAfterDragFromFooter: CustomDOMRect = - await getDialogPartRect(page, dialogSelector); - - // Dialog should not have been moved after drag from footer - expect(dialogRectAfterDragFromFooter.x).toBeCloseTo( - dialogRectBeforeDragFromFooter.x, - threshold - ); - expect(dialogRectAfterDragFromFooter.y).toBeCloseTo( - dialogRectBeforeDragFromFooter.y, - threshold - ); - }); -}); diff --git a/src/components/dialog/tests/behaviors/modal.e2e.ts b/src/components/dialog/tests/behaviors/modal.e2e.ts deleted file mode 100644 index 5b15b2fdc..000000000 --- a/src/components/dialog/tests/behaviors/modal.e2e.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; - -describe("[ch-dialog][modal]", () => { - let page: E2EPage; - let chDialogRef: E2EElement; - - beforeEach(async () => { - // Initialize the page with a button - page = await newE2EPage({ - html: `Dialog Content`, - failOnConsoleError: true - }); - await page.setViewport({ width: 1920, height: 1080 }); - // Apply generous viewport dimensions to ensure that a mouse click - // effectively is triggered on the body. - - chDialogRef = await page.find("ch-dialog"); - - // Attach click listener to document.body in browser context - await page.evaluate(() => { - document.documentElement.style.blockSize = "100%"; - document.body.style.blockSize = "100%"; - document.body.style.margin = "0"; - - const windowRef = window as any; - windowRef.bodyClickCount = 0; - document.body.addEventListener("click", () => { - windowRef.bodyClickCount += 1; - }); - }); - await page.waitForChanges(); - }); - - it("should not block body clicks when dialog is non-modal", async () => { - chDialogRef.setProperty("modal", false); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - await page.mouse.click(10, 10); - await page.waitForChanges(); - - const clickCount = await page.evaluate(() => { - return (window as any).bodyClickCount; - }); - - expect(clickCount).toBe(1); - }); - - it("should block body clicks when dialog is non-modal", async () => { - chDialogRef.setProperty("modal", true); - await page.waitForChanges(); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - await page.mouse.click(10, 10); - await page.waitForChanges(); - - const clickCount = await page.evaluate(() => { - return (window as any).bodyClickCount; - }); - - expect(clickCount).toBe(0); - }); -}); diff --git a/src/components/dialog/tests/behaviors/position.e2e.ts b/src/components/dialog/tests/behaviors/position.e2e.ts deleted file mode 100644 index fe25735e5..000000000 --- a/src/components/dialog/tests/behaviors/position.e2e.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; -import { getDialogPartRect, CustomDOMRect } from "../_utils"; - -const viewPortWidth = 1920; -const viewPortHeight = 1080; -let dialogDOMRect: CustomDOMRect; - -describe("[ch-dialog][modal]", () => { - let page: E2EPage; - let chDialogRef: E2EElement; - - beforeEach(async () => { - page = await newE2EPage({ - html: `Dialog Content`, - failOnConsoleError: true - }); - await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); - - chDialogRef = await page.find("ch-dialog"); - }); - - it.skip("should be centered on the viewport", async () => { - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - dialogDOMRect = await getDialogPartRect(page); - - const estimatedXPosition = viewPortWidth / 2 - dialogDOMRect.width / 2; - const estimatedYPosition = viewPortHeight / 2 - dialogDOMRect.height / 2; - const threshold = 0; // 0 means compare integers, not decimal places. - - expect(dialogDOMRect.x).toBeCloseTo(estimatedXPosition, threshold); - expect(dialogDOMRect.y).toBeCloseTo(estimatedYPosition, threshold); - }); - - it.skip("should consider '--ch-dialog-block-start' and '--ch-dialog-inline-start' in the initial position", async () => { - const chDialogBlockStart = 50; - const chDialogInlineStart = 50; - const threshold = 0; // 0 means compare integers, not decimal places. - - await page.evaluate( - (x, y) => { - const style = document.createElement("style"); - style.textContent = ` - ch-dialog { - --ch-dialog-inline-start: ${x}px; - --ch-dialog-block-start: ${y}px; - } - `; - document.head.appendChild(style); - }, - chDialogInlineStart, - chDialogBlockStart - ); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - dialogDOMRect = await getDialogPartRect(page); - - expect(dialogDOMRect.x).toBeCloseTo(chDialogBlockStart, threshold); - expect(dialogDOMRect.y).toBeCloseTo(chDialogInlineStart, threshold); - }); - - it.skip("should mantain position after close", async () => { - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - - dialogDOMRect = await getDialogPartRect(page); - - const estimatedXPosition = viewPortWidth / 2 - dialogDOMRect.width / 2; - const estimatedYPosition = viewPortHeight / 2 - dialogDOMRect.height / 2; - const threshold = 0; // 0 means compare integers, not decimal places. - - expect(dialogDOMRect.x).toBeCloseTo(estimatedXPosition, threshold); - expect(dialogDOMRect.y).toBeCloseTo(estimatedYPosition, threshold); - }); -}); diff --git a/src/components/dialog/tests/behaviors/resize.e2e.ts b/src/components/dialog/tests/behaviors/resize.e2e.ts deleted file mode 100644 index 03dedbe5a..000000000 --- a/src/components/dialog/tests/behaviors/resize.e2e.ts +++ /dev/null @@ -1,332 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; -import { getDialogPartRect, CustomDOMRect } from "../_utils"; - -// Note: Some issues have been experienced after doing more than one drag -// on a single "it" test. To avoid this issues, dragging tests have been -// done in separate "it" tests. - -const viewPortWidth = 1920; -const viewPortHeight = 1080; - -describe("[ch-dialog][resize]", () => { - let page: E2EPage; - let chDialogRef: E2EElement; - - const dialogSelector = "dialog"; - - // Corners - const cornerBlockStartInlineStartSelector = - "[part~='corner-block-start-inline-start']"; - const cornerBlockStartInlineEndSelector = - "[part~='corner-block-start-inline-end']"; - const cornerBlockEndInlineStartSelector = - "[part~='corner-block-end-inline-start']"; - const cornerBlockEndInlineEndSelector = - "[part~='corner-block-end-inline-end']"; - - // Edges - const edgeBlockEndSelector = "[part~='edge-block-end']"; - const edgeBlockStartSelector = "[part~='edge-block-start']"; - const edgeInlineEndSelector = "[part~='edge-inline-end']"; - const edgeInlineStartSelector = "[part~='edge-inline-start']"; - const chDialogBlockStart = 100; - const chDialogInlineStart = 100; - const threshold = 0; - const resizeBlockValue = 20; - const resizeInlineValue = 20; - - beforeEach(async () => { - page = await newE2EPage({ - html: ` - - Dialog Content - `, - failOnConsoleError: true - }); - await page.setViewport({ width: viewPortWidth, height: viewPortHeight }); - chDialogRef = await page.find("ch-dialog"); - - chDialogRef.setProperty("resizable", true); - await page.waitForChanges(); - - // Set the dialog x and y on 0, to make it easier to evaluate drag changes. - await page.evaluate( - (x, y) => { - const style = document.createElement("style"); - style.textContent = ` - ch-dialog { - --ch-dialog-inline-start: ${x}px; - --ch-dialog-block-start: ${y}px; - } - `; - document.head.appendChild(style); - }, - chDialogInlineStart, - chDialogBlockStart - ); - - chDialogRef.setProperty("show", true); - await page.waitForChanges(); - }); - - // Inline Edges - - it.skip("should resize by dragging from 'edge-inline-start' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const edgeInlineStartRect: CustomDOMRect = await getDialogPartRect( - page, - edgeInlineStartSelector - ); - - const resizeTo = edgeInlineStartRect.x - resizeInlineValue; - // resize - await page.mouse.move(edgeInlineStartRect.x, edgeInlineStartRect.y); - await page.mouse.down(); - await page.mouse.move(resizeTo, 0); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.width).toBeCloseTo( - dialogRectBeforeResize.width + resizeInlineValue, - threshold - ); - }); - - it.skip("should resize by dragging from 'edge-inline-end' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const edgeInlineEndRect: CustomDOMRect = await getDialogPartRect( - page, - edgeInlineEndSelector - ); - - const resizeTo = edgeInlineEndRect.x + resizeInlineValue; - // resize - await page.mouse.move(edgeInlineEndRect.x, edgeInlineEndRect.y); - await page.mouse.down(); - await page.mouse.move(resizeTo, edgeInlineEndRect.y); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.width).toBeCloseTo( - dialogRectBeforeResize.width + resizeInlineValue, - threshold - ); - }); - - // Block Edges - - it.skip("should resize by dragging from 'edge-block-start' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const edgeBlockStartRect: CustomDOMRect = await getDialogPartRect( - page, - edgeBlockStartSelector - ); - - const resizeTo = edgeBlockStartRect.y - resizeBlockValue; - - // resize - await page.mouse.move(edgeBlockStartRect.x, edgeBlockStartRect.y); - await page.mouse.down(); - await page.mouse.move(edgeBlockStartRect.x, resizeTo); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); - - it.skip("should resize by dragging from 'edge-block-end' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const edgeBlockEndRect: CustomDOMRect = await getDialogPartRect( - page, - edgeBlockEndSelector - ); - - const resizeTo = edgeBlockEndRect.y + resizeBlockValue; - - // resize - await page.mouse.move(edgeBlockEndRect.x, edgeBlockEndRect.y); - await page.mouse.down(); - await page.mouse.move(edgeBlockEndRect.x, resizeTo); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); - - // Corner "top-left" - - it.skip("should resize by dragging from 'corner-block-start-inline-start' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const cornerBlockStartInlineStartRect: CustomDOMRect = - await getDialogPartRect(page, cornerBlockStartInlineStartSelector); - - const resizeToX = cornerBlockStartInlineStartRect.x - resizeBlockValue; - const resizeToY = cornerBlockStartInlineStartRect.y - resizeInlineValue; - - // resize - await page.mouse.move( - cornerBlockStartInlineStartRect.x, - cornerBlockStartInlineStartRect.y - ); - await page.mouse.down(); - await page.mouse.move(resizeToX, resizeToY); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); - - // Corner "top-right" - - it.skip("should resize by dragging from 'corner-block-start-inline-end' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const cornerBlockStartInlineEndRect: CustomDOMRect = - await getDialogPartRect(page, cornerBlockStartInlineEndSelector); - - const resizeToX = cornerBlockStartInlineEndRect.x + resizeBlockValue; - const resizeToY = cornerBlockStartInlineEndRect.y - resizeInlineValue; - - // resize - await page.mouse.move( - cornerBlockStartInlineEndRect.x, - cornerBlockStartInlineEndRect.y - ); - await page.mouse.down(); - await page.mouse.move(resizeToX, resizeToY); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); - - // Corner "bottom-left" - - it.skip("should resize by dragging from 'corner-block-end-inline-start' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - const cornerBlockEndInlineStartRect: CustomDOMRect = - await getDialogPartRect(page, cornerBlockEndInlineStartSelector); - - const resizeToX = cornerBlockEndInlineStartRect.x - resizeBlockValue; - const resizeToY = cornerBlockEndInlineStartRect.y + resizeInlineValue; - - // resize - await page.mouse.move( - cornerBlockEndInlineStartRect.x, - cornerBlockEndInlineStartRect.y - ); - await page.mouse.down(); - await page.mouse.move(resizeToX, resizeToY); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); - - // Corner "bottom-right" - - it("should resize by dragging from 'corner-block-end-inline-end' part", async () => { - // get parts coordinates - const dialogRectBeforeResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - const cornerBlockEndInlineEndRect: CustomDOMRect = await getDialogPartRect( - page, - cornerBlockEndInlineEndSelector - ); - - const resizeToX = cornerBlockEndInlineEndRect.x + resizeBlockValue; - const resizeToY = cornerBlockEndInlineEndRect.y + resizeInlineValue; - - // resize - await page.mouse.move( - cornerBlockEndInlineEndRect.x, - cornerBlockEndInlineEndRect.y - ); - await page.mouse.down(); - await page.mouse.move(resizeToX, resizeToY, { steps: 10 }); - await page.mouse.up(); - - const dialogRectAfterResize: CustomDOMRect = await getDialogPartRect( - page, - dialogSelector - ); - - expect(dialogRectAfterResize.height).toBeCloseTo( - dialogRectBeforeResize.height + resizeBlockValue, - threshold - ); - }); -}); diff --git a/src/components/dialog/tests/parts.e2e.ts b/src/components/dialog/tests/parts.e2e.ts deleted file mode 100644 index 89f2d5a7b..000000000 --- a/src/components/dialog/tests/parts.e2e.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; - -describe("[ch-dialog][parts]", () => { - let page: E2EPage; - let dialogRef: E2EElement; - - const testPart = ( - selector: string, - elementDescription: string, - part: string - ) => - it(`the ${elementDescription} should have the "${part}" part`, async () => { - const elementRef = await page.find(`ch-dialog >>> ${selector}`); - expect(elementRef).not.toBeNull(); - // expect(elementRef.getAttribute("part")).toContain(part); - }); - - beforeEach(async () => { - page = await newE2EPage({ - html: ``, - failOnConsoleError: true - }); - dialogRef = await page.find("ch-dialog"); - - // Some parts depend on properties not being false or undefined: - await dialogRef.setProperty("showHeader", true); - await dialogRef.setProperty("caption", "Some Caption"); - await dialogRef.setProperty("show", true); - await dialogRef.setProperty("resizable", true); - await page.waitForChanges(); - }); - - testPart('[part="dialog"]', "dialog", "dialog"); - testPart('[part="header"]', "header", "header"); - testPart('[part="caption"]', "caption", "caption"); - testPart('[part="close-button"]', "close-button", "close-button"); - testPart('[part="content"]', "content", "content"); - - // Edges and corners - testPart( - '[part="edge edge-block-start"]', - "edge-block-start", - "edge-block-start" - ); - testPart( - '[part="edge edge-inline-end"]', - "edge-inline-end", - "edge-inline-end" - ); - testPart('[part="edge edge-block-end"]', "edge-block-end", "edge-block-end"); - testPart( - '[part="edge edge-inline-start"]', - "edge-inline-start", - "edge-inline-start" - ); - - testPart( - '[part="corner corner-block-start-inline-start"]', - "corner-block-start-inline-start", - "corner-block-start-inline-start" - ); - testPart( - '[part="corner corner-block-start-inline-end"]', - "corner-block-start-inline-end", - "corner-block-start-inline-end" - ); - testPart( - '[part="corner corner-block-end-inline-start"]', - "corner-block-end-inline-start", - "corner-block-end-inline-start" - ); - testPart( - '[part="corner corner-block-end-inline-end"]', - "corner-block-end-inline-end", - "corner-block-end-inline-end" - ); -}); diff --git a/src/components/dialog/tests/slots.e2e.ts b/src/components/dialog/tests/slots.e2e.ts deleted file mode 100644 index 8364f568b..000000000 --- a/src/components/dialog/tests/slots.e2e.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { E2EPage, newE2EPage } from "@stencil/core/testing"; - -describe("[ch-dialog][slots]", () => { - let page: E2EPage; - let dialogRef; - beforeEach(async () => { - page = await newE2EPage({ - html: ``, - failOnConsoleError: true - }); - dialogRef = await page.find("ch-dialog"); - }); - - // default slot - it("should have a slot element by default", async () => { - const slot = await page.find("ch-dialog >>> slot"); - expect(slot).not.toBeNull(); - }); - - // footer slot - it("should render a 'footer' slot if showFooter is true", async () => { - dialogRef.setProperty("showFooter", true); - await page.waitForChanges(); - const slotFooter = await page.find( - "ch-dialog >>> dialog footer slot[name='footer']" - ); - expect(slotFooter).not.toBeNull(); - }); - - it("should not render a 'footer' slot if showFooter is false", async () => { - dialogRef.setProperty("showFooter", false); - await page.waitForChanges(); - const slotFooter = await page.find( - "ch-dialog >>> dialog footer slot[name='footer']" - ); - expect(slotFooter).toBeNull(); - }); - - it("should render multiple elements in the default slot", async () => { - await page.setContent(` - -
First Content
-
Second Content
-
- `); - const contents = await page.findAll("ch-dialog .default-slot-content"); - expect(contents.length).toBe(2); - expect(contents[0].textContent).toBe("First Content"); - expect(contents[1].textContent).toBe("Second Content"); - }); -}); From 6c083cbf9e6c224c8c4a9574cd4798056d74bc2c Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:07:20 +0100 Subject: [PATCH 08/31] [ch-popover] Update `hidden` prop name, in favor of `show` Update property and showcase file accordingly --- src/components/popover/popover.scss | 9 +++--- src/components/popover/popover.tsx | 29 +++++++++---------- .../components/popover/popover.showcase.tsx | 14 ++++----- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/components/popover/popover.scss b/src/components/popover/popover.scss index 39ff8aad7..72b24b314 100644 --- a/src/components/popover/popover.scss +++ b/src/components/popover/popover.scss @@ -126,7 +126,7 @@ $ch-popover-y--same-layer: calc( --ch-popover-dragged-y: 0px; --ch-popover-rtl: 1; - display: grid; // Necessary to support gap property + display: none; // Necessary when the popover is not placed in a new top layer. For example, // when nesting dropdowns @@ -167,12 +167,13 @@ $ch-popover-y--same-layer: calc( translate: max(0px, var(--ch-popover-x)) max(0px, var(--ch-popover-y)); } +:host([show]) { + display: grid; // Necessary to support gap property +} + // - - - - - - - - - - - - - - - - // Hidden // - - - - - - - - - - - - - - - - -:host([hidden]) { - display: none; -} // // content-visibility: hidden is faster for rendering the content back, as // // it preserves the rendering state of the content (display: none does not) diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index fe8c9b5bd..4cc62a6e1 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -371,22 +371,21 @@ export class ChPopover { * Specifies whether the popover is hidden or visible. */ // eslint-disable-next-line @stencil-community/ban-default-true - @Prop({ mutable: true, reflect: true }) hidden = true; - @Watch("hidden") - handleHiddenChange(newHiddenValue: boolean) { + @Prop({ mutable: true, reflect: true }) show = false; + @Watch("show") + handleShowChange(newShowValue: boolean) { // Schedule update for watchers this.#checkBorderSizeWatcher = true; this.#checkPositionWatcher = true; // Update the popover visualization - if (newHiddenValue) { + if (newShowValue) { + this.#showPopover(); + } else { if (this.firstLayer) { this.#avoidFlickeringInTheNextRender(true); } - this.el.hidePopover(); - } else { - this.#showPopover(); } } @@ -494,7 +493,7 @@ export class ChPopover { ) { this.#removeClickOutsideWatcher(); - this.hidden = true; + this.show = false; this.popoverClosed.emit(); } }; @@ -503,7 +502,7 @@ export class ChPopover { if (event.code === KEY_CODES.ESCAPE) { this.#removeClickOutsideWatcher(); - this.hidden = true; + this.show = false; this.popoverClosed.emit(); } }; @@ -563,7 +562,7 @@ export class ChPopover { }; #setPositionWatcher = () => { - if (!this.actionElement || this.hidden) { + if (!this.actionElement || !this.show) { this.#removePositionWatcher(); return; } @@ -802,7 +801,7 @@ export class ChPopover { #handlePopoverToggle = (event: ToggleEvent) => { const willBeHidden = !(event.newState === "open"); - this.hidden = willBeHidden; + this.show = !willBeHidden; // Emit events only when the action is committed by the user if (willBeHidden) { @@ -1017,7 +1016,7 @@ export class ChPopover { */ // eslint-disable-next-line @stencil-community/own-props-must-be-private #setBorderSizeWatcher = () => { - if (!this.resizable || this.hidden) { + if (!this.resizable || !this.show) { this.#removeBorderSizeWatcher(); return; } @@ -1144,7 +1143,7 @@ export class ChPopover { this.#setPositionWatcher(); this.#setBorderSizeWatcher(); - if (!this.hidden) { + if (this.show) { this.#showPopover(); } } @@ -1170,7 +1169,7 @@ export class ChPopover { } render() { - const canAddListeners = !this.hidden; + const canAddListeners = this.show; return ( {this.resizable && - !this.hidden && [ + this.show && [
= {}; let buttonRef: HTMLButtonElement; const handlePopoverOpened = () => { - state.hidden = false; + state.show = true; // TODO: Until we support external slots in the ch-flexible-layout-render, // this is a hack to update the render of the widget and thus re-render the @@ -23,7 +23,7 @@ const handlePopoverOpened = () => { }; const handlePopoverClosed = () => { - state.hidden = true; + state.show = false; // TODO: Until we support external slots in the ch-flexible-layout-render, // this is a hack to update the render of the widget and thus re-render the @@ -45,7 +45,7 @@ const render = () => ( blockSizeMatch={state.blockSizeMatch} class="popover-secondary" closeOnClickOutside={state.closeOnClickOutside} - hidden={state.hidden} + show={state.show} inlineAlign={state.inlineAlign} inlineSizeMatch={state.inlineSizeMatch} mode={state.mode} @@ -97,9 +97,9 @@ const showcaseRenderProperties: ShowcaseRenderProperties = columns: 2, properties: [ { - id: "hidden", - caption: "Hidden", - value: true, + id: "show", + caption: "Show", + value: false, type: "boolean" }, { @@ -314,7 +314,7 @@ const showcasePropertiesInfo: ShowcaseTemplatePropertyInfo type: "string" }, { name: "closeOnClickOutside", defaultValue: false, type: "boolean" }, - { name: "hidden", defaultValue: true, type: "boolean" }, + { name: "show", defaultValue: false, type: "boolean" }, { name: "inlineAlign", defaultValue: "center", type: "string" }, { name: "inlineSizeMatch", defaultValue: "content", type: "string" }, { name: "mode", defaultValue: "auto", type: "string" }, From ac7d50b7f2ea77f4b10ccc6631a3541fbdd5ae4f Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:28:38 +0100 Subject: [PATCH 09/31] [ch-tooltip] Update `ch-popover` `hidden` property name --- src/components/tooltip/tooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx index 58c60c499..b3282b399 100644 --- a/src/components/tooltip/tooltip.tsx +++ b/src/components/tooltip/tooltip.tsx @@ -200,7 +200,7 @@ export class ChTooltip implements ComponentInterface { actionElement={this.#getActionElement()} blockAlign={this.blockAlign} closeOnClickOutside - hidden={false} + show={true} inlineAlign={this.inlineAlign} mode="manual" // We need to sync the visible state when the popover is closed by an From 025c19efc17a575db1187bb77d78963997e4add4 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:30:14 +0100 Subject: [PATCH 10/31] [ch-combo-box] Update `ch-popover` `hidden` property name --- src/components/combo-box/combo-box.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/combo-box/combo-box.tsx b/src/components/combo-box/combo-box.tsx index fa49b5f49..167a8bac7 100644 --- a/src/components/combo-box/combo-box.tsx +++ b/src/components/combo-box/combo-box.tsx @@ -972,7 +972,7 @@ export class ChComboBoxRender blockAlign="outside-end" inlineAlign={this.popoverInlineAlign} closeOnClickOutside - hidden={false} + show={true} popover="manual" resizable={this.resizable} inlineSizeMatch="action-element-as-minimum" From 1c64f843c8c9e51702eacb4a0b09387ac33dc56c Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:31:11 +0100 Subject: [PATCH 11/31] [ch-dropdown] Update `ch-popover` `hidden` property name --- src/components/dropdown/internal/dropdown/dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dropdown/internal/dropdown/dropdown.tsx b/src/components/dropdown/internal/dropdown/dropdown.tsx index 24b7462ee..acca9d5ae 100644 --- a/src/components/dropdown/internal/dropdown/dropdown.tsx +++ b/src/components/dropdown/internal/dropdown/dropdown.tsx @@ -473,7 +473,7 @@ export class ChDropDown implements ChComponent { actionElement={this.#mainAction as HTMLButtonElement} firstLayer={this.level === -1 || this.actionGroupParent} popover="manual" - hidden={!this.expanded} + show={this.expanded} inlineAlign={xAlignMapping} blockAlign={yAlignMapping} > From 477eab24ca16e9cbe3d46009ffc7d7b5282285e2 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:32:35 +0100 Subject: [PATCH 12/31] Update readme.md --- src/components/popover/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/popover/readme.md b/src/components/popover/readme.md index 3245e69fa..f8537fda3 100644 --- a/src/components/popover/readme.md +++ b/src/components/popover/readme.md @@ -19,13 +19,13 @@ relative to an element, but placed on the top layer using `position: fixed`. | `blockSizeMatch` | `block-size-match` | Specifies how the popover adapts its block size. - "content": The block size of the control will be determined by its content block size. - "action-element": The block size of the control will match the block size of the `actionElement`. - "action-element-as-minimum": The minimum block size of the control will match the block size of the `actionElement`. If the control is resized at runtime, only the "action-element-as-minimum" value will still work. | `"action-element" \| "action-element-as-minimum" \| "content"` | `"content"` | | `closeOnClickOutside` | `close-on-click-outside` | This property only applies for `"manual"` mode. In native popovers, when using `"manual"` mode the popover doesn't close when clicking outside the control. This property allows to close the popover when clicking outside in `"manual"` mode. With this, the popover will close if the click is triggered on any other element than the popover and the `actionElement`. It will also close if the "Escape" key is pressed. | `boolean` | `false` | | `firstLayer` | `first-layer` | `true` if the control is not stacked with another top layer. | `boolean` | `true` | -| `hidden` | `hidden` | Specifies whether the popover is hidden or visible. | `boolean` | `true` | | `inlineAlign` | `inline-align` | Specifies the inline alignment of the window. | `"center" \| "inside-end" \| "inside-start" \| "outside-end" \| "outside-start"` | `"center"` | | `inlineSizeMatch` | `inline-size-match` | Specifies how the popover adapts its inline size. - "content": The inline size of the control will be determined by its content inline size. - "action-element": The inline size of the control will match the inline size of the `actionElement`. - "action-element-as-minimum": The minimum inline size of the control will match the inline size of the `actionElement`. If the control is resized at runtime, only the "action-element-as-minimum" value will still work. | `"action-element" \| "action-element-as-minimum" \| "content"` | `"content"` | | `mode` | `popover` | Popovers that have the `"auto"` state can be "light dismissed" by selecting outside the popover area, and generally only allow one popover to be displayed on-screen at a time. By contrast, `"manual"` popovers must always be explicitly hidden, but allow for use cases such as nested popovers in menus. | `"auto" \| "manual"` | `"auto"` | | `overflowBehavior` | `overflow-behavior` | Specifies how the popover behaves when the content overflows the window size. - "overflow": The control won't implement any behavior if the content overflows. - "add-scroll": The control will place a scroll if the content overflows. | `"add-scroll" \| "overflow"` | `"overflow"` | | `positionTry` | `position-try` | Specifies an alternate position to try when the control overflows the window. | `"flip-block" \| "flip-inline" \| "none"` | `"none"` | | `resizable` | `resizable` | Specifies whether the control can be resized. If `true` the control can be resized at runtime by dragging the edges or corners. | `boolean` | `false` | +| `show` | `show` | Specifies whether the popover is hidden or visible. | `boolean` | `false` | ## Events From 7f47eaeb90ab126ad6718afc223ea57fdbb32def Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:34:07 +0100 Subject: [PATCH 13/31] [combo-box] Apply PR review suggestions --- src/components/combo-box/combo-box.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/combo-box/combo-box.tsx b/src/components/combo-box/combo-box.tsx index 167a8bac7..963e2dd9c 100644 --- a/src/components/combo-box/combo-box.tsx +++ b/src/components/combo-box/combo-box.tsx @@ -972,7 +972,7 @@ export class ChComboBoxRender blockAlign="outside-end" inlineAlign={this.popoverInlineAlign} closeOnClickOutside - show={true} + show popover="manual" resizable={this.resizable} inlineSizeMatch="action-element-as-minimum" From cbcc818b1ef8778ea771fb24e161d5917940d255 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:28:01 +0100 Subject: [PATCH 14/31] `[ch-dialog]` Apply PR review suggestions --- src/components/dialog/dialog.tsx | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index 2d862bdae..928341c99 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -271,9 +271,9 @@ export class ChDialog { * Specifies whether the dialog is shown or not. */ // eslint-disable-next-line @stencil-community/ban-default-true - @Prop({ mutable: true }) show = false; + @Prop({ mutable: true }) show: boolean = false; @Watch("show") - handleShowChange(show: boolean) { + showChanged(show: boolean) { // Schedule update for watchers this.#checkBorderSizeWatcher = true; this.#checkPositionWatcher = true; @@ -750,7 +750,7 @@ export class ChDialog { ref={el => (this.#dialogRef = el)} > {this.showHeader && ( -
-
+
)} -
+
{this.showFooter && ( -
+
+
)} {this.resizable && this.show && [
, // Top
, // Right
, // Bottom
, // Left
, // Top Left
, // Top Right
, // Bottom Left
Date: Fri, 3 Jan 2025 15:31:27 +0100 Subject: [PATCH 15/31] `[ch-popover]` Apply PR review suggestions --- src/components/popover/popover.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index 4cc62a6e1..c72a33495 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -371,9 +371,9 @@ export class ChPopover { * Specifies whether the popover is hidden or visible. */ // eslint-disable-next-line @stencil-community/ban-default-true - @Prop({ mutable: true, reflect: true }) show = false; + @Prop({ mutable: true, reflect: true }) show: boolean = false; @Watch("show") - handleShowChange(newShowValue: boolean) { + showChanged(newShowValue: boolean) { // Schedule update for watchers this.#checkBorderSizeWatcher = true; this.#checkPositionWatcher = true; @@ -800,14 +800,14 @@ export class ChPopover { }; #handlePopoverToggle = (event: ToggleEvent) => { - const willBeHidden = !(event.newState === "open"); - this.show = !willBeHidden; + const willBeOpen = event.newState === "open"; + this.show = willBeOpen; // Emit events only when the action is committed by the user - if (willBeHidden) { - this.popoverClosed.emit(); - } else { + if (willBeOpen) { this.popoverOpened.emit(); + } else { + this.popoverClosed.emit(); } }; From 9e81a8f008e8cc11cc5feed5c6a2f2687f740115 Mon Sep 17 00:00:00 2001 From: Bruno Sastre <54677414+bsastregx@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:32:09 +0100 Subject: [PATCH 16/31] `[ch-tooltip]` Apply PR review suggestions --- src/components/tooltip/tooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/tooltip/tooltip.tsx b/src/components/tooltip/tooltip.tsx index b3282b399..efc37f42d 100644 --- a/src/components/tooltip/tooltip.tsx +++ b/src/components/tooltip/tooltip.tsx @@ -200,7 +200,7 @@ export class ChTooltip implements ComponentInterface { actionElement={this.#getActionElement()} blockAlign={this.blockAlign} closeOnClickOutside - show={true} + show inlineAlign={this.inlineAlign} mode="manual" // We need to sync the visible state when the popover is closed by an From a9f56ede8f06076fe2eb85d214879081d34594f6 Mon Sep 17 00:00:00 2001 From: Bruno Sastre Date: Fri, 3 Jan 2025 17:50:51 +0100 Subject: [PATCH 17/31] `[ch-dialog]` Include some semantic validation --- src/components/dialog/tests/semantic.e2e.ts | 86 +++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/components/dialog/tests/semantic.e2e.ts diff --git a/src/components/dialog/tests/semantic.e2e.ts b/src/components/dialog/tests/semantic.e2e.ts new file mode 100644 index 000000000..cf8df2063 --- /dev/null +++ b/src/components/dialog/tests/semantic.e2e.ts @@ -0,0 +1,86 @@ +import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing"; + +describe("[ch-dialog][semantic]", () => { + let page: E2EPage; + let chDialogRef: E2EElement; + + beforeEach(async () => { + page = await newE2EPage({ + html: ``, + failOnConsoleError: true + }); + + chDialogRef = await page.find("ch-dialog"); + + // Set properties + chDialogRef.setProperty("showHeader", true); + await page.waitForChanges(); + chDialogRef.setProperty("showFooter", true); + await page.waitForChanges(); + // chDialogRef.setProperty("resizable", true); + // await page.waitForChanges(); + chDialogRef.setProperty("show", true); + await page.waitForChanges(); + }); + + it("should not render a header element", async () => { + const headerEl = await page.find("ch-dialog >>> header"); + expect(headerEl).toBeNull(); + }); + + it("should not render a footer element", async () => { + const footerEl = await page.find("ch-dialog >>> footer"); + expect(footerEl).toBeNull(); + }); + + it("should not include a role='banner' attribute on the part='header' element", async () => { + const headerPartRef = await page.find("ch-dialog >>> [part='header']"); + await page.waitForChanges(); + expect(headerPartRef).not.toBeNull(); + + const roleAttribute = headerPartRef.getAttribute("role"); + expect(roleAttribute).not.toBe("banner"); + }); + + it("should not include a role='contentinfo' attribute on the part='footer' element", async () => { + const footerPartRef = await page.find("ch-dialog >>> [part='footer']"); + await page.waitForChanges(); + expect(footerPartRef).not.toBeNull(); + + const roleAttribute = footerPartRef.getAttribute("role"); + expect(roleAttribute).not.toBe("contentinfo"); + }); + + it.skip("should not include an id on any of the resize-bar elements", async () => { + const partsSelectors = [ + "ch-dialog >>> [part='edge edge-block-start']", + "ch-dialog >>> [part='edge edge-inline-end']", + "ch-dialog >>> [part='edge edge-block-end']", + "ch-dialog >>> [part='edge edge-inline-start']", + "ch-dialog >>> [part='corner corner-block-start-inline-start']", + "ch-dialog >>> [part='corner corner-block-start-inline-end']", + "ch-dialog >>> [part='corner corner-block-end-inline-start']", + "ch-dialog >>> [part='corner corner-block-end-inline-end']" + ]; + + for (const part of partsSelectors) { + const resizePartRef = await page.find(part); + + if (resizePartRef) { + const idAttribute = await resizePartRef.getAttribute("id"); + expect(idAttribute).toBeNull(); + } else { + throw new Error(`Element with part "${part}" not found.`); + } + } + }); + + it("should not include an id on the part='content' element", async () => { + const contentPartRef = await page.find("ch-dialog >>> [part='content']"); + await page.waitForChanges(); + expect(contentPartRef).not.toBeNull(); + + const contentId = contentPartRef.getAttribute("id"); + expect(contentId).toBeNull(); + }); +}); From ac7721939ebbeeb2da588f6a854eb42962b97dd6 Mon Sep 17 00:00:00 2001 From: Bruno Sastre Date: Fri, 3 Jan 2025 20:46:47 +0100 Subject: [PATCH 18/31] `[ch-dialog]` improve `#setBorderSizeWatcher` call on `componentWillRender` - Add a `setTimeout` in order to force execution after browser paint. This fixes some E2E tests random fails, due to `this.#resizeLayer` being `undefined`. --- src/components/dialog/dialog.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index 928341c99..e81dd5228 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -351,9 +351,7 @@ export class ChDialog { this.#checkBorderSizeWatcher = false; // Wait until the resize edges have been rendered - requestAnimationFrame(() => { - this.#setBorderSizeWatcher(); - }); + requestAnimationFrame(() => setTimeout(this.#setBorderSizeWatcher)); } } From 4c62eb5e5c7068d2cf2324120577ec8541cd8fb3 Mon Sep 17 00:00:00 2001 From: Bruno Sastre Date: Fri, 3 Jan 2025 20:47:10 +0100 Subject: [PATCH 19/31] `[ch-dialog]`Rename tests filename --- .../{semantic.e2e.ts => constraints.e2e.ts} | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) rename src/components/dialog/tests/{semantic.e2e.ts => constraints.e2e.ts} (73%) diff --git a/src/components/dialog/tests/semantic.e2e.ts b/src/components/dialog/tests/constraints.e2e.ts similarity index 73% rename from src/components/dialog/tests/semantic.e2e.ts rename to src/components/dialog/tests/constraints.e2e.ts index cf8df2063..3184e58c4 100644 --- a/src/components/dialog/tests/semantic.e2e.ts +++ b/src/components/dialog/tests/constraints.e2e.ts @@ -17,8 +17,8 @@ describe("[ch-dialog][semantic]", () => { await page.waitForChanges(); chDialogRef.setProperty("showFooter", true); await page.waitForChanges(); - // chDialogRef.setProperty("resizable", true); - // await page.waitForChanges(); + chDialogRef.setProperty("resizable", true); + await page.waitForChanges(); chDialogRef.setProperty("show", true); await page.waitForChanges(); }); @@ -51,7 +51,31 @@ describe("[ch-dialog][semantic]", () => { expect(roleAttribute).not.toBe("contentinfo"); }); - it.skip("should not include an id on any of the resize-bar elements", async () => { + it("should not include an id on any of the resize-bar elements", async () => { + const partsSelectors = [ + "ch-dialog >>> [part='edge edge-block-start']", + "ch-dialog >>> [part='edge edge-inline-end']", + "ch-dialog >>> [part='edge edge-block-end']", + "ch-dialog >>> [part='edge edge-inline-start']", + "ch-dialog >>> [part='corner corner-block-start-inline-start']", + "ch-dialog >>> [part='corner corner-block-start-inline-end']", + "ch-dialog >>> [part='corner corner-block-end-inline-start']", + "ch-dialog >>> [part='corner corner-block-end-inline-end']" + ]; + + for (const part of partsSelectors) { + const resizePartRef = await page.find(part); + + if (resizePartRef) { + const idAttribute = await resizePartRef.getAttribute("id"); + expect(idAttribute).toBeNull(); + } else { + throw new Error(`Element with part "${part}" not found.`); + } + } + }); + + it("test ", async () => { const partsSelectors = [ "ch-dialog >>> [part='edge edge-block-start']", "ch-dialog >>> [part='edge edge-inline-end']", From 779ad2caa602d47052b12a6b39c65020c638bd42 Mon Sep 17 00:00:00 2001 From: Bruno Sastre Date: Fri, 3 Jan 2025 22:27:25 +0100 Subject: [PATCH 20/31] `[ch-dialog]` Apply PR review suggestions --- src/components/dialog/dialog.tsx | 1 - .../dialog/tests/constraints.e2e.ts | 66 ++++--------------- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/components/dialog/dialog.tsx b/src/components/dialog/dialog.tsx index e81dd5228..69d4b7683 100644 --- a/src/components/dialog/dialog.tsx +++ b/src/components/dialog/dialog.tsx @@ -762,7 +762,6 @@ export class ChDialog { )}