fix(reorder-group): add ability to reorder items inside shadow (#17747)

- adds ability to reorder items from within a shadow dom component
- adds interactive test

fixes #17746
This commit is contained in:
Adam LaCombe
2019-03-26 17:00:44 -04:00
committed by Brandy Carney
parent a8a48a4ca4
commit 352797e932
5 changed files with 170 additions and 1 deletions

View File

@@ -64,7 +64,7 @@ export class ReorderGroup implements ComponentInterface {
}
this.gesture = (await import('../../utils/gesture')).createGesture({
el: this.doc.body,
el: this.el,
queue: this.queue,
gestureName: 'reorder',
gesturePriority: 110,

View File

@@ -0,0 +1,60 @@
import * as pd from '@stencil/core/dist/testing/puppeteer/puppeteer-declarations';
import { newE2EPage } from '@stencil/core/testing';
import { getElementProperty, queryDeep } from '../../../../utils/test/utils';
import { moveReorderItem } from '../test.utils';
test('reorder: interactive', async () => {
const page = await newE2EPage({
url: '/src/components/reorder-group/test/interactive?ionic:_testing=true'
});
const compares = [];
compares.push(await page.compareScreenshot('reorder: interactive before move'));
const items = await page.$$('ion-reorder');
const getItemId = await getElementProperty(items[0], 'id');
expect(getItemId).toEqual('item-0');
await moveItem(getItemId, page, 'down', 1);
const itemsAfterFirstMove = await page.$$('ion-reorder');
expect(await getElementProperty(itemsAfterFirstMove[0], 'id')).toEqual('item-1');
await moveItem(getItemId, page, 'up', 1);
const itemsAfterSecondMove = await page.$$('ion-reorder');
expect(await getElementProperty(itemsAfterSecondMove[0], 'id')).toEqual('item-0');
compares.push(await page.compareScreenshot('reorder: interactive after move; before shadow move'));
const shadowDomList = await queryDeep(page, 'test-reorder-list-shadow-dom', 'ion-list');
const itemsInShadowRoot = await shadowDomList.$$('ion-reorder');
const getShadowItemId = await getElementProperty(itemsInShadowRoot[0], 'id');
expect(getShadowItemId).toEqual('item-0');
await moveItem(getShadowItemId, page, 'down', 1, 'test-reorder-list-shadow-dom', 'ion-list');
const itemsInShadowRootAfterFirstMove = await shadowDomList.$$('ion-reorder');
expect(await getElementProperty(itemsInShadowRootAfterFirstMove[0], 'id')).toEqual('item-1');
await moveItem(getShadowItemId, page, 'up', 1, 'test-reorder-list-shadow-dom', 'ion-list');
const itemsInShadowRootAfterSecondMove = await shadowDomList.$$('ion-reorder');
expect(await getElementProperty(itemsInShadowRootAfterSecondMove[0], 'id')).toEqual('item-0');
compares.push(await page.compareScreenshot('reorder: interactive after shadow move'));
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});
async function moveItem(id: string, page: pd.E2EPage, direction: 'up' | 'down' = 'up', numberOfSpaces = 1, ...parentSelectors: string[]) {
try {
await moveReorderItem(`#${id}`, page, direction, numberOfSpaces, ...parentSelectors);
} catch (err) {
throw err;
}
}

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Reorder - Interactive</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/core.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script src="../../../../../dist/ionic.js"></script>
</head>
<body>
<ion-list><ion-reorder-group disabled="false"></ion-reorder-group></ion-list>
<script>
class TestReorderListShadowDom extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
wrapper.innerHTML = `<ion-list><ion-reorder-group disabled="false"></ion-reorder-group></ion-list>`;
shadow.appendChild(wrapper);
}
}
customElements.define('test-reorder-list-shadow-dom', TestReorderListShadowDom);
function addReorderItem(list) {
const reorderItem = document.createElement('ion-reorder');
reorderItem.innerHTML = `<ion-item>
<ion-label>
<h2>Item ${list.childElementCount}</h2>
</ion-label>
</ion-item>`;
reorderItem.id = `item-${list.childElementCount}`;
list.appendChild(reorderItem);
}
const testShadowDomEl = document.createElement('test-reorder-list-shadow-dom');
document.body.appendChild(testShadowDomEl);
let lists = Array.from(document.getElementsByTagName('ion-reorder-group'));
lists.push(testShadowDomEl.shadowRoot.querySelector('ion-reorder-group'));
for (var i = 0; i < lists.length; i++) {
lists[i].addEventListener('ionItemReorder', ({detail}) => detail.complete(true));
for (var j = 0; j < 3; j++) addReorderItem(lists[i]);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
import * as pd from '@stencil/core/dist/testing/puppeteer/puppeteer-declarations';
import { dragElementBy, queryDeep } from '../../../utils/test/utils';
/**
* Moves a reorder item by simulating a drag event
*/
export async function moveReorderItem(id: string, page: pd.E2EPage, direction: 'up' | 'down' = 'up', numberOfSpaces = 1, ...parentSelectors: string[]) {
try {
const reorderItem = parentSelectors && parentSelectors.length > 0 ? await (await queryDeep(page, ...parentSelectors)).$(id) : await page.$(id);
if (!reorderItem) { throw new Error('Reorder Item is undefined'); }
const boundingBox = await reorderItem.boundingBox();
if (!boundingBox) { throw new Error('Reorder Item bounding box is undefined'); }
await dragElementBy(reorderItem, page, 0, (direction === 'up') ? -(boundingBox.height * numberOfSpaces) : (boundingBox.height * numberOfSpaces));
} catch (err) {
throw err;
}
}

View File

@@ -1,3 +1,6 @@
import { E2EPage } from '@stencil/core/testing';
import { ElementHandle } from 'puppeteer';
export function generateE2EUrl(component: string, type: string, rtl = false): string {
let url = `/src/components/${component}/test/${type}?ionic:_testing=true`;
if (rtl) {
@@ -14,6 +17,16 @@ export function cleanScreenshotName(screenshotName: string): string {
.toLowerCase();
}
/**
* Gets the value of a property on an element
*/
export const getElementProperty = async (element: any, property: string): Promise<string> => {
const getProperty = await element.getProperty(property);
if (!getProperty) { return ''; }
return getProperty.jsonValue();
};
/**
* Listens for an event and fires a callback
* @param page - The Puppeteer `page` object
@@ -87,3 +100,22 @@ export const waitForFunctionTestContext = async (fn: any, params: any, interval
}, interval);
});
};
/**
* Pierce through shadow roots
* https://github.com/GoogleChrome/puppeteer/issues/858#issuecomment-359763824
*/
export async function queryDeep(page: E2EPage, ...selectors: string[]): Promise<ElementHandle> {
const shadowSelectorFn = (el: Element, selector: string): Element | null => (el && el.shadowRoot) && el.shadowRoot.querySelector(selector);
return new Promise(async resolve => {
const [ firstSelector, ...restSelectors ] = selectors;
let parentElement = await page.$(firstSelector);
for (const selector of restSelectors) {
parentElement = await page.evaluateHandle(shadowSelectorFn, parentElement, selector) as any;
}
if (parentElement) { resolve(parentElement); }
});
}