mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
fix(input, textarea): padding is now added to content so inputs scroll above keyboard (#25849)
resolves #18532
This commit is contained in:
@ -1,23 +1,42 @@
|
||||
import { getScrollElement, scrollByPoint } from '../../content';
|
||||
import { raf } from '../../helpers';
|
||||
import type { KeyboardResizeOptions } from '../../native/keyboard';
|
||||
import { KeyboardResize } from '../../native/keyboard';
|
||||
|
||||
import { relocateInput, SCROLL_AMOUNT_PADDING } from './common';
|
||||
import { getScrollData } from './scroll-data';
|
||||
import { setScrollPadding, setClearScrollPaddingListener } from './scroll-padding';
|
||||
|
||||
let currentPadding = 0;
|
||||
|
||||
export const enableScrollAssist = (
|
||||
componentEl: HTMLElement,
|
||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||
contentEl: HTMLElement | null,
|
||||
footerEl: HTMLIonFooterElement | null,
|
||||
keyboardHeight: number
|
||||
keyboardHeight: number,
|
||||
enableScrollPadding: boolean,
|
||||
keyboardResize: KeyboardResizeOptions | undefined
|
||||
) => {
|
||||
/**
|
||||
* Scroll padding should only be added if:
|
||||
* 1. The global scrollPadding config option
|
||||
* is set to true.
|
||||
* 2. The native keyboard resize mode is either "none"
|
||||
* (keyboard overlays webview) or undefined (resize
|
||||
* information unavailable)
|
||||
* Resize info is available on Capacitor 4+
|
||||
*/
|
||||
const addScrollPadding =
|
||||
enableScrollPadding && (keyboardResize === undefined || keyboardResize.mode === KeyboardResize.None);
|
||||
|
||||
/**
|
||||
* When the input is about to receive
|
||||
* focus, we need to move it to prevent
|
||||
* mobile Safari from adjusting the viewport.
|
||||
*/
|
||||
const focusIn = () => {
|
||||
jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight);
|
||||
const focusIn = async () => {
|
||||
jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight, addScrollPadding);
|
||||
};
|
||||
componentEl.addEventListener('focusin', focusIn, true);
|
||||
|
||||
@ -31,7 +50,8 @@ const jsSetFocus = async (
|
||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||
contentEl: HTMLElement | null,
|
||||
footerEl: HTMLIonFooterElement | null,
|
||||
keyboardHeight: number
|
||||
keyboardHeight: number,
|
||||
enableScrollPadding: boolean
|
||||
) => {
|
||||
if (!contentEl && !footerEl) {
|
||||
return;
|
||||
@ -42,6 +62,22 @@ const jsSetFocus = async (
|
||||
// the text input is in a safe position that doesn't
|
||||
// require it to be scrolled into view, just set focus now
|
||||
inputEl.focus();
|
||||
|
||||
/**
|
||||
* Even though the input does not need
|
||||
* scroll assist, we should preserve the
|
||||
* the scroll padding as users could be moving
|
||||
* focus from an input that needs scroll padding
|
||||
* to an input that does not need scroll padding.
|
||||
* If we remove the scroll padding now, users will
|
||||
* see the page jump.
|
||||
*/
|
||||
if (enableScrollPadding && contentEl) {
|
||||
currentPadding += scrollData.scrollAmount;
|
||||
setScrollPadding(contentEl, currentPadding);
|
||||
setClearScrollPaddingListener(inputEl, contentEl, () => (currentPadding = 0));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -58,6 +94,17 @@ const jsSetFocus = async (
|
||||
*/
|
||||
raf(() => componentEl.click());
|
||||
|
||||
/**
|
||||
* If enabled, we can add scroll padding to
|
||||
* the bottom of the content so that scroll assist
|
||||
* has enough room to scroll the input above
|
||||
* the keyboard.
|
||||
*/
|
||||
if (enableScrollPadding && contentEl) {
|
||||
currentPadding += scrollData.scrollAmount;
|
||||
setScrollPadding(contentEl, currentPadding);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
let scrollContentTimeout: any;
|
||||
const scrollContent = async () => {
|
||||
@ -80,6 +127,15 @@ const jsSetFocus = async (
|
||||
|
||||
// ensure this is the focused input
|
||||
inputEl.focus();
|
||||
|
||||
/**
|
||||
* When the input is about to be blurred
|
||||
* we should set a timeout to remove
|
||||
* any scroll padding.
|
||||
*/
|
||||
if (enableScrollPadding) {
|
||||
setClearScrollPaddingListener(inputEl, contentEl, () => (currentPadding = 0));
|
||||
}
|
||||
};
|
||||
|
||||
const doubleKeyboardEventListener = () => {
|
||||
|
@ -1,51 +1,61 @@
|
||||
import { findClosestIonContent } from '../../content';
|
||||
|
||||
const PADDING_TIMER_KEY = '$ionPaddingTimer';
|
||||
|
||||
export const enableScrollPadding = (keyboardHeight: number) => {
|
||||
const doc = document;
|
||||
|
||||
const onFocusin = (ev: any) => {
|
||||
setScrollPadding(ev.target, keyboardHeight);
|
||||
};
|
||||
const onFocusout = (ev: any) => {
|
||||
setScrollPadding(ev.target, 0);
|
||||
};
|
||||
|
||||
doc.addEventListener('focusin', onFocusin);
|
||||
doc.addEventListener('focusout', onFocusout);
|
||||
|
||||
return () => {
|
||||
doc.removeEventListener('focusin', onFocusin);
|
||||
doc.removeEventListener('focusout', onFocusout);
|
||||
};
|
||||
};
|
||||
|
||||
const setScrollPadding = (input: HTMLElement, keyboardHeight: number) => {
|
||||
if (input.tagName !== 'INPUT') {
|
||||
return;
|
||||
}
|
||||
if (input.parentElement && input.parentElement.tagName === 'ION-INPUT') {
|
||||
return;
|
||||
}
|
||||
if (input.parentElement?.parentElement?.tagName === 'ION-SEARCHBAR') {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = findClosestIonContent(input);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
const timer = (el as any)[PADDING_TIMER_KEY];
|
||||
/**
|
||||
* Scroll padding adds additional padding to the bottom
|
||||
* of ion-content so that there is enough scroll space
|
||||
* for an input to be scrolled above the keyboard. This
|
||||
* is needed in environments where the webview does not
|
||||
* resize when the keyboard opens.
|
||||
*
|
||||
* Example: If an input at the bottom of ion-content is
|
||||
* focused, there is no additional scrolling space below
|
||||
* it, so the input cannot be scrolled above the keyboard.
|
||||
* Scroll padding fixes this by adding padding equal to the
|
||||
* height of the keyboard to the bottom of the content.
|
||||
*
|
||||
* Common environments where this is needed:
|
||||
* - Mobile Safari: The keyboard overlays the content
|
||||
* - Capacitor/Cordova on iOS: The keyboard overlays the content
|
||||
* when the KeyboardResize mode is set to 'none'.
|
||||
*/
|
||||
export const setScrollPadding = (contentEl: HTMLElement, paddingAmount: number, clearCallback?: () => void) => {
|
||||
const timer = (contentEl as any)[PADDING_TIMER_KEY];
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
if (keyboardHeight > 0) {
|
||||
el.style.setProperty('--keyboard-offset', `${keyboardHeight}px`);
|
||||
if (paddingAmount > 0) {
|
||||
contentEl.style.setProperty('--keyboard-offset', `${paddingAmount}px`);
|
||||
} else {
|
||||
(el as any)[PADDING_TIMER_KEY] = setTimeout(() => {
|
||||
el.style.setProperty('--keyboard-offset', '0px');
|
||||
(contentEl as any)[PADDING_TIMER_KEY] = setTimeout(() => {
|
||||
contentEl.style.setProperty('--keyboard-offset', '0px');
|
||||
if (clearCallback) {
|
||||
clearCallback();
|
||||
}
|
||||
}, 120);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When an input is about to be focused,
|
||||
* set a timeout to clear any scroll padding
|
||||
* on the content. Note: The clearing
|
||||
* is done on a timeout so that if users
|
||||
* are moving focus from one input to the next
|
||||
* then re-adding scroll padding to the new
|
||||
* input with cancel the timeout to clear the
|
||||
* scroll padding.
|
||||
*/
|
||||
export const setClearScrollPaddingListener = (
|
||||
inputEl: HTMLInputElement | HTMLTextAreaElement,
|
||||
contentEl: HTMLElement | null,
|
||||
doneCallback: () => void
|
||||
) => {
|
||||
const clearScrollPadding = () => {
|
||||
if (contentEl) {
|
||||
setScrollPadding(contentEl, 0, doneCallback);
|
||||
}
|
||||
};
|
||||
|
||||
inputEl.addEventListener('focusout', clearScrollPadding, { once: true });
|
||||
};
|
||||
|
@ -88,6 +88,24 @@
|
||||
keyboardHeight: 250,
|
||||
},
|
||||
};
|
||||
|
||||
const params = new URLSearchParams(window.location.href.split('?')[1]);
|
||||
const resizeMode = params.get('resizeMode');
|
||||
|
||||
if (resizeMode) {
|
||||
window.Capacitor = {
|
||||
isPluginAvailable: (plugin) => plugin === 'Keyboard',
|
||||
Plugins: {
|
||||
Keyboard: {
|
||||
getResizeMode: () => {
|
||||
return Promise.resolve({
|
||||
mode: resizeMode,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
@ -1,60 +1,178 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { Locator } from '@playwright/test';
|
||||
import { KeyboardResize } from '@utils/native/keyboard';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('scroll-assist', () => {
|
||||
const getScrollPosition = async (contentEl: Locator) => {
|
||||
return await contentEl.evaluate(async (el: HTMLIonContentElement) => {
|
||||
const scrollEl = await el.getScrollElement();
|
||||
const getScrollPosition = async (contentEl: Locator) => {
|
||||
return await contentEl.evaluate(async (el: HTMLIonContentElement) => {
|
||||
const scrollEl = await el.getScrollElement();
|
||||
|
||||
return scrollEl.scrollTop;
|
||||
});
|
||||
};
|
||||
return scrollEl.scrollTop;
|
||||
});
|
||||
};
|
||||
|
||||
test.describe('scroll-assist', () => {
|
||||
let scrollAssistFixture: ScrollAssistFixture;
|
||||
test.beforeEach(async ({ page, skip }) => {
|
||||
skip.rtl();
|
||||
skip.mode('md', 'Scroll utils are only needed on iOS mode');
|
||||
skip.browser('firefox');
|
||||
skip.browser('chromium');
|
||||
|
||||
await page.goto('/src/utils/input-shims/hacks/test');
|
||||
});
|
||||
test('should not activate when input is above the keyboard', async ({ page }) => {
|
||||
const input = page.locator('#input-above-keyboard');
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(await getScrollPosition(content)).toBe(0);
|
||||
|
||||
await input.click();
|
||||
await expect(input.locator('input')).toBeFocused();
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(await getScrollPosition(content)).toBe(0);
|
||||
scrollAssistFixture = new ScrollAssistFixture(page);
|
||||
});
|
||||
|
||||
test('should activate when input is below the keyboard', async ({ page }) => {
|
||||
const input = page.locator('#input-below-keyboard');
|
||||
const content = page.locator('ion-content');
|
||||
test.describe('scroll-assist: basic functionality', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto();
|
||||
});
|
||||
test('should not activate when input is above the keyboard', async () => {
|
||||
await scrollAssistFixture.expectNotToHaveScrollAssist(
|
||||
'#input-above-keyboard',
|
||||
'#input-above-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
await expect(await getScrollPosition(content)).toBe(0);
|
||||
test('should activate when input is below the keyboard', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#input-below-keyboard',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
await input.click({ force: true });
|
||||
await page.waitForChanges();
|
||||
await expect(input.locator('input:not(.cloned-input)')).toBeFocused();
|
||||
|
||||
await expect(await getScrollPosition(content)).not.toBe(0);
|
||||
test('should activate even when not explicitly tapping input', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollAssist(
|
||||
'#item-below-keyboard ion-label',
|
||||
'#input-below-keyboard input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-assist: scroll-padding', () => {
|
||||
test.describe('scroll-padding: browser/cordova', () => {
|
||||
test.beforeEach(async () => {
|
||||
await scrollAssistFixture.goto();
|
||||
});
|
||||
test('should add scroll padding for an input at the bottom of the scroll container', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
|
||||
test('should activate even when not explicitly tapping input', async ({ page }) => {
|
||||
const label = page.locator('#item-below-keyboard ion-label');
|
||||
const input = page.locator('#input-below-keyboard');
|
||||
const content = page.locator('ion-content');
|
||||
test('should keep scroll padding even when switching between inputs', async () => {
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
|
||||
await expect(await getScrollPosition(content)).toBe(0);
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#textarea-outside-viewport',
|
||||
'#textarea-outside-viewport textarea:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
test.describe('scroll-padding: webview resizing', () => {
|
||||
test('should add scroll padding when webview resizing is "none"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.None);
|
||||
|
||||
await label.click({ force: true });
|
||||
await page.waitForChanges();
|
||||
await expect(input.locator('input:not(.cloned-input)')).toBeFocused();
|
||||
await scrollAssistFixture.expectToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "body"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Body);
|
||||
|
||||
await expect(await getScrollPosition(content)).not.toBe(0);
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "ionic"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Ionic);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
test('should not add scroll padding when webview resizing is "native"', async () => {
|
||||
await scrollAssistFixture.goto(KeyboardResize.Native);
|
||||
|
||||
await scrollAssistFixture.expectNotToHaveScrollPadding(
|
||||
'#input-outside-viewport',
|
||||
'#input-outside-viewport input:not(.cloned-input)'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScrollAssistFixture {
|
||||
readonly page: E2EPage;
|
||||
private content!: Locator;
|
||||
|
||||
constructor(page: E2EPage) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async goto(resizeMode?: KeyboardResize) {
|
||||
let url = `/src/utils/input-shims/hacks/test`;
|
||||
if (resizeMode !== undefined) {
|
||||
url += `?resizeMode=${resizeMode}`;
|
||||
}
|
||||
|
||||
await this.page.goto(url);
|
||||
|
||||
this.content = this.page.locator('ion-content');
|
||||
}
|
||||
|
||||
private async focusInput(interactiveSelector: string, inputSelector: string) {
|
||||
const { page } = this;
|
||||
const interactive = page.locator(interactiveSelector);
|
||||
const input = page.locator(inputSelector);
|
||||
|
||||
await interactive.click({ force: true });
|
||||
await expect(input).toBeFocused();
|
||||
await page.waitForChanges();
|
||||
}
|
||||
|
||||
private getScrollPosition() {
|
||||
const { content } = this;
|
||||
|
||||
return getScrollPosition(content);
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollAssist(interactiveSelector: string, inputSelector: string) {
|
||||
await expect(await this.getScrollPosition()).toBe(0);
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(await this.getScrollPosition()).not.toBe(0);
|
||||
}
|
||||
|
||||
async expectToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).not.toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
|
||||
async expectNotToHaveScrollPadding(interactiveSelector: string, inputSelector: string) {
|
||||
const { content } = this;
|
||||
|
||||
await this.focusInput(interactiveSelector, inputSelector);
|
||||
|
||||
await expect(content).toHaveCSS('--keyboard-offset', '0px');
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
import type { Config } from '../../interface';
|
||||
import { findClosestIonContent } from '../content';
|
||||
import { componentOnReady } from '../helpers';
|
||||
import { Keyboard } from '../native/keyboard';
|
||||
|
||||
import { enableHideCaretOnScroll } from './hacks/hide-caret';
|
||||
import { enableInputBlurring } from './hacks/input-blurring';
|
||||
import { enableScrollAssist } from './hacks/scroll-assist';
|
||||
import { enableScrollPadding } from './hacks/scroll-padding';
|
||||
|
||||
const INPUT_BLURRING = true;
|
||||
const SCROLL_ASSIST = true;
|
||||
const SCROLL_PADDING = true;
|
||||
const HIDE_CARET = true;
|
||||
|
||||
export const startInputShims = (config: Config) => {
|
||||
export const startInputShims = async (config: Config) => {
|
||||
const doc = document;
|
||||
const keyboardHeight = config.getNumber('keyboardHeight', 290);
|
||||
const scrollAssist = config.getBoolean('scrollAssist', true);
|
||||
@ -24,6 +23,16 @@ export const startInputShims = (config: Config) => {
|
||||
const hideCaretMap = new WeakMap<HTMLElement, () => void>();
|
||||
const scrollAssistMap = new WeakMap<HTMLElement, () => void>();
|
||||
|
||||
/**
|
||||
* Grab the native keyboard resize configuration
|
||||
* and pass it to scroll assist. Scroll assist requires
|
||||
* that we adjust the input right before the input
|
||||
* is about to be focused. If we called `Keyboard.getResizeMode`
|
||||
* on focusin in scroll assist, we could potentially adjust the
|
||||
* input too late since this call is async.
|
||||
*/
|
||||
const keyboardResizeMode = await Keyboard.getResizeMode();
|
||||
|
||||
const registerInput = async (componentEl: HTMLElement) => {
|
||||
await new Promise((resolve) => componentOnReady(componentEl, resolve));
|
||||
|
||||
@ -55,7 +64,15 @@ export const startInputShims = (config: Config) => {
|
||||
scrollAssist &&
|
||||
!scrollAssistMap.has(componentEl)
|
||||
) {
|
||||
const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight);
|
||||
const rmFn = enableScrollAssist(
|
||||
componentEl,
|
||||
inputEl,
|
||||
scrollEl,
|
||||
footerEl,
|
||||
keyboardHeight,
|
||||
scrollPadding,
|
||||
keyboardResizeMode
|
||||
);
|
||||
scrollAssistMap.set(componentEl, rmFn);
|
||||
}
|
||||
};
|
||||
@ -82,10 +99,6 @@ export const startInputShims = (config: Config) => {
|
||||
enableInputBlurring();
|
||||
}
|
||||
|
||||
if (scrollPadding && SCROLL_PADDING) {
|
||||
enableScrollPadding(keyboardHeight);
|
||||
}
|
||||
|
||||
// Input might be already loaded in the DOM before ion-device-hacks did.
|
||||
// At this point we need to look for all of the inputs not registered yet
|
||||
// and register them.
|
||||
|
27
core/src/utils/native/keyboard.ts
Normal file
27
core/src/utils/native/keyboard.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { win } from '../window';
|
||||
|
||||
// Interfaces source: https://capacitorjs.com/docs/apis/keyboard#interfaces
|
||||
export interface KeyboardResizeOptions {
|
||||
mode: KeyboardResize;
|
||||
}
|
||||
|
||||
export enum KeyboardResize {
|
||||
Body = 'body',
|
||||
Ionic = 'ionic',
|
||||
Native = 'native',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export const Keyboard = {
|
||||
getEngine() {
|
||||
return (win as any)?.Capacitor?.isPluginAvailable('Keyboard') && (win as any)?.Capacitor.Plugins.Keyboard;
|
||||
},
|
||||
getResizeMode(): Promise<KeyboardResizeOptions | undefined> {
|
||||
const engine = this.getEngine();
|
||||
if (!engine || !engine.getResizeMode) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return engine.getResizeMode();
|
||||
},
|
||||
};
|
@ -22,7 +22,22 @@ export const goto = async (page: Page, url: string, options: any, testInfo: Test
|
||||
const formattedRtl = urlToParams.get('rtl') ?? rtl;
|
||||
const ionicTesting = urlToParams.get('ionic:_testing') ?? _testing;
|
||||
|
||||
const formattedUrl = `${splitUrl[0]}?ionic:_testing=${ionicTesting}&ionic:mode=${formattedMode}&rtl=${formattedRtl}`;
|
||||
/**
|
||||
* Pass through other custom query params
|
||||
*/
|
||||
urlToParams.delete('ionic:mode');
|
||||
urlToParams.delete('rtl');
|
||||
urlToParams.delete('ionic:_testing');
|
||||
|
||||
/**
|
||||
* Convert remaining query params to a string.
|
||||
* Be sure to call decodeURIComponent to decode
|
||||
* characters such as &.
|
||||
*/
|
||||
const remainingQueryParams = decodeURIComponent(urlToParams.toString());
|
||||
const remainingQueryParamsString = remainingQueryParams == '' ? '' : `&${remainingQueryParams}`;
|
||||
|
||||
const formattedUrl = `${splitUrl[0]}?ionic:_testing=${ionicTesting}&ionic:mode=${formattedMode}&rtl=${formattedRtl}${remainingQueryParamsString}`;
|
||||
|
||||
testInfo.annotations.push({
|
||||
type: 'mode',
|
||||
|
Reference in New Issue
Block a user