From 35b1a459294657b609f683457e71babd4673a3a8 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 11 Sep 2024 09:39:37 -0700 Subject: [PATCH] fix(range): disable scroll when range is being dragged (#29241) Issue number: internal --------- ## What is the current behavior? There are a few tests that were disabled due to being flaky from gestures. ## What is the new behavior? While fixing the tests, I found a bug that the scroll was never being disabled on scroll. Additionally, we were not taking into account that a custom scroll target could be used so it was never disabled either. - Fixed the flaky tests. - Content doesn't scroll when range is being dragged. - Content can be either `ion-content` or a custom scroll target. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information [Preview for `ion-content`](https://ionic-framework-git-fw-2873-ionic1.vercel.app/src/components/range/test/scroll) [Preview for custom scroll target](https://ionic-framework-git-fw-2873-ionic1.vercel.app/src/components/range/test/scroll-target) How to test: 1. Open either of the previews 2. Render the screen with the device simulator from the browser 3. Verify that you can scroll the page 4. Drag the range but don't let go 5. Verify that you cannot scroll the page 6. Repeat steps 2-5 with the other preview --- core/src/components/range/range.tsx | 7 +- .../components/range/test/range-events.e2e.ts | 105 +++++++++--------- .../range/test/scroll-target/range.e2e.ts | 19 ++-- .../components/range/test/scroll/index.html | 73 ++++++++++++ .../components/range/test/scroll/range.e2e.ts | 46 ++++++++ 5 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 core/src/components/range/test/scroll/index.html create mode 100644 core/src/components/range/test/scroll/range.e2e.ts diff --git a/core/src/components/range/range.tsx b/core/src/components/range/range.tsx index baccd1f9859..da4c2d75344 100644 --- a/core/src/components/range/range.tsx +++ b/core/src/components/range/range.tsx @@ -325,7 +325,8 @@ export class Range implements ComponentInterface { this.setupGesture(); } - this.contentEl = findClosestIonContent(this.el); + const ionContent = findClosestIonContent(this.el); + this.contentEl = ionContent?.querySelector('.ion-content-scroll-host') ?? ionContent; } disconnectedCallback() { @@ -418,7 +419,7 @@ export class Range implements ComponentInterface { * * This only needs to be done once. */ - if (contentEl && this.initialContentScrollY === undefined) { + if (contentEl && this.pressedKnob === undefined) { this.initialContentScrollY = disableContentScrollY(contentEl); } @@ -469,7 +470,7 @@ export class Range implements ComponentInterface { * * The user can now scroll on the view in the next gesture event. */ - if (contentEl && initialContentScrollY !== undefined) { + if (contentEl && this.pressedKnob !== undefined) { resetContentScrollY(contentEl, initialContentScrollY); } diff --git a/core/src/components/range/test/range-events.e2e.ts b/core/src/components/range/test/range-events.e2e.ts index 6dae3232e76..bfbdfac48e4 100644 --- a/core/src/components/range/test/range-events.e2e.ts +++ b/core/src/components/range/test/range-events.e2e.ts @@ -7,10 +7,7 @@ import { configs, dragElementBy, test } from '@utils/test/playwright'; configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('range: events:'), () => { test.describe('range: knob events', () => { - /** - * The mouse events are flaky on CI - */ - test.fixme('should emit start/end events', async ({ page }) => { + test('should emit start/end events', async ({ page }) => { /** * Requires padding to prevent the knob from being clipped. * If it's clipped, then the value might be one off. @@ -31,23 +28,34 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => const rangeEl = page.locator('ion-range'); - await dragElementBy(rangeEl, page, 300, 0); - await page.waitForChanges(); - /** - * dragElementBy defaults to starting the drag from the middle of the el, - * so the start value should jump to 50 despite the range defaulting to 20. + * Verify both events fire if range is dragged. */ - expect(rangeStart).toHaveReceivedEventDetail({ value: 50 }); + await dragElementBy(rangeEl, page, 180); + + await rangeStart.next(); + await rangeEnd.next(); + + // Once the knob is dragged, the start event should fire with + // the initial value. + expect(rangeStart).toHaveReceivedEventDetail({ value: 20 }); + // Once the knob is released, the end event should fire with + // the final value. expect(rangeEnd).toHaveReceivedEventDetail({ value: 100 }); /** - * Verify both events fire if range is clicked without dragging. + * Verify both events fire if range is tapped without dragging. */ await dragElementBy(rangeEl, page, 0, 0); - await page.waitForChanges(); - expect(rangeStart).toHaveReceivedEventDetail({ value: 50 }); + await rangeStart.next(); + await rangeEnd.next(); + + // Once the tap is released, the start event should fire with + // the initial value. + expect(rangeStart).toHaveReceivedEventDetail({ value: 100 }); + // Once the tap is released, the end event should fire with + // the final value. expect(rangeEnd).toHaveReceivedEventDetail({ value: 50 }); }); @@ -99,31 +107,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(rangeEndSpy.length).toBe(1); }); - - // TODO FW-2873 - test.skip('should not scroll when the knob is swiped', async ({ page, skip }) => { - skip.browser('webkit', 'mouse.wheel is not available in WebKit'); - - await page.goto(`/src/components/range/test/basic`, config); - - const knobEl = page.locator('ion-range#stacked-range .range-knob-handle'); - const scrollEl = page.locator('ion-content .inner-scroll'); - - expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - - await dragElementBy(knobEl, page, 30, 0, undefined, undefined, false); - - /** - * Do not use scrollToBottom() or other scrolling methods - * on ion-content as those will update the scroll position. - * Setting scrollTop still works even with overflow-y: hidden. - * However, simulating a user gesture should not scroll the content. - */ - await page.mouse.wheel(0, 100); - await page.waitForChanges(); - - expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - }); }); test.describe('ionChange', () => { @@ -151,14 +134,26 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(ionChangeSpy).toHaveReceivedEventTimes(0); }); - // TODO FW-2873 - test.skip('should emit when the knob is released', async ({ page }) => { - await page.setContent(``, config); + test('should emit when the knob is released', async ({ page }) => { + /** + * Requires padding to prevent the knob from being clipped. + * If it's clipped, then the value might be one off. + * For example, if the knob is clipped on the right, then the value + * will be 99 instead of 100. + */ + await page.setContent( + ` +
+ +
+ `, + config + ); - const rangeHandle = page.locator('ion-range .range-knob-handle'); + const rangeEl = page.locator('ion-range'); const ionChangeSpy = await page.spyOnEvent('ionChange'); - await dragElementBy(rangeHandle, page, 100, 0); + await dragElementBy(rangeEl, page, 100); await ionChangeSpy.next(); @@ -196,16 +191,26 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => }); test.describe('ionInput', () => { - // TODO(FW-2873) Enable this test when touch events/gestures are better supported in Playwright - test.skip('should emit when the knob is dragged', async ({ page }) => { - await page.setContent(``, config); + test('should emit when the knob is dragged', async ({ page }) => { + /** + * Requires padding to prevent the knob from being clipped. + * If it's clipped, then the value might be one off. + * For example, if the knob is clipped on the right, then the value + * will be 99 instead of 100. + */ + await page.setContent( + ` +
+ +
+ `, + config + ); - const rangeHandle = page.locator('ion-range .range-knob-handle'); + const rangeEl = page.locator('ion-range'); const ionInputSpy = await page.spyOnEvent('ionInput'); - await rangeHandle.hover(); - - await dragElementBy(rangeHandle, page, 100, 0, undefined, undefined, false); + await dragElementBy(rangeEl, page, 100); await ionInputSpy.next(); diff --git a/core/src/components/range/test/scroll-target/range.e2e.ts b/core/src/components/range/test/scroll-target/range.e2e.ts index 990c4cd4f2e..fbb57f7d2a9 100644 --- a/core/src/components/range/test/scroll-target/range.e2e.ts +++ b/core/src/components/range/test/scroll-target/range.e2e.ts @@ -1,29 +1,26 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { configs, test, dragElementBy } from '@utils/test/playwright'; -// TODO FW-2873 /** * This behavior does not vary across modes/directions. */ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { - test.describe.skip(title('range: scroll-target'), () => { + test.describe(title('range: scroll-target'), () => { test('should not scroll when the knob is swiped in custom scroll target', async ({ page, skip }) => { + /** + * The Playwright team has stated that they will not implement this feature: + * https://github.com/microsoft/playwright/issues/28755 + */ skip.browser('webkit', 'mouse.wheel is not available in WebKit'); await page.goto(`/src/components/range/test/scroll-target`, config); - const knobEl = page.locator('ion-range .range-knob-handle'); + const rangeEl = page.locator('ion-range'); const scrollEl = page.locator('.ion-content-scroll-host'); expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - const box = (await knobEl.boundingBox())!; - const centerX = box.x + box.width / 2; - const centerY = box.y + box.height / 2; - - await page.mouse.move(centerX, centerY); - await page.mouse.down(); - await page.mouse.move(centerX + 30, centerY); + await dragElementBy(rangeEl, page, 100, 0, undefined, undefined, false); /** * Do not use scrollToBottom() or other scrolling methods diff --git a/core/src/components/range/test/scroll/index.html b/core/src/components/range/test/scroll/index.html new file mode 100644 index 00000000000..9c02c54d26a --- /dev/null +++ b/core/src/components/range/test/scroll/index.html @@ -0,0 +1,73 @@ + + + + + Range - Scroll + + + + + + + + + + + + Range - Scroll + + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur faucibus nulla a nunc tincidunt semper. Nam + nibh lorem, pharetra ac ex ac, tempus fringilla est. Aenean tincidunt ipsum pellentesque, consequat libero id, + feugiat leo. In vestibulum faucibus velit, non tincidunt erat tincidunt in. Donec a diam sed nisl convallis + maximus. Aenean cursus sagittis lorem vitae tristique. Pellentesque pellentesque, quam eget lobortis finibus, + lectus lorem maximus purus, quis sagittis tortor sem sed tellus. +

+ + +
Range Label
+ Start + End +
+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur faucibus nulla a nunc tincidunt semper. Nam + nibh lorem, pharetra ac ex ac, tempus fringilla est. Aenean tincidunt ipsum pellentesque, consequat libero id, + feugiat leo. In vestibulum faucibus velit, non tincidunt erat tincidunt in. Donec a diam sed nisl convallis + maximus. Aenean cursus sagittis lorem vitae tristique. Pellentesque pellentesque, quam eget lobortis finibus, + lectus lorem maximus purus, quis sagittis tortor sem sed tellus. +

+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur faucibus nulla a nunc tincidunt semper. Nam + nibh lorem, pharetra ac ex ac, tempus fringilla est. Aenean tincidunt ipsum pellentesque, consequat libero id, + feugiat leo. In vestibulum faucibus velit, non tincidunt erat tincidunt in. Donec a diam sed nisl convallis + maximus. Aenean cursus sagittis lorem vitae tristique. Pellentesque pellentesque, quam eget lobortis finibus, + lectus lorem maximus purus, quis sagittis tortor sem sed tellus. +

+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur faucibus nulla a nunc tincidunt semper. Nam + nibh lorem, pharetra ac ex ac, tempus fringilla est. Aenean tincidunt ipsum pellentesque, consequat libero id, + feugiat leo. In vestibulum faucibus velit, non tincidunt erat tincidunt in. Donec a diam sed nisl convallis + maximus. Aenean cursus sagittis lorem vitae tristique. Pellentesque pellentesque, quam eget lobortis finibus, + lectus lorem maximus purus, quis sagittis tortor sem sed tellus. +

+ +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur faucibus nulla a nunc tincidunt semper. Nam + nibh lorem, pharetra ac ex ac, tempus fringilla est. Aenean tincidunt ipsum pellentesque, consequat libero id, + feugiat leo. In vestibulum faucibus velit, non tincidunt erat tincidunt in. Donec a diam sed nisl convallis + maximus. Aenean cursus sagittis lorem vitae tristique. Pellentesque pellentesque, quam eget lobortis finibus, + lectus lorem maximus purus, quis sagittis tortor sem sed tellus.! +

+
+
+ + diff --git a/core/src/components/range/test/scroll/range.e2e.ts b/core/src/components/range/test/scroll/range.e2e.ts new file mode 100644 index 00000000000..9353d5fe2d5 --- /dev/null +++ b/core/src/components/range/test/scroll/range.e2e.ts @@ -0,0 +1,46 @@ +import { expect } from '@playwright/test'; +import { configs, dragElementBy, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions. + */ +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('range: scroll'), () => { + test('should not scroll when the knob is being dragged', async ({ page, skip }) => { + /** + * The Playwright team has stated that they will not implement this feature: + * https://github.com/microsoft/playwright/issues/28755 + */ + skip.browser('webkit', 'mouse.wheel is not available in WebKit'); + + /** + * Requires padding to prevent the knob from being clipped. + * If it's clipped, then the value might be one off. + * For example, if the knob is clipped on the right, then the value + * will be 99 instead of 100. + * + * The ion-content is also required to be taller than the viewport + * to allow for scrolling. + */ + await page.goto(`/src/components/range/test/scroll`, config); + + const rangeEl = page.locator('ion-range'); + const scrollEl = page.locator('ion-content .inner-scroll'); + + expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); + + await dragElementBy(rangeEl, page, 100, 0, undefined, undefined, false); + + /** + * Do not use scrollToBottom() or other scrolling methods + * on ion-content as those will update the scroll position. + * Setting scrollTop still works even with overflow-y: hidden. + * However, simulating a user gesture should not scroll the content. + */ + await page.mouse.wheel(0, 100); + await page.waitForChanges(); + + expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); + }); + }); +});