@angular/cdk
Version:
Angular Material Component Development Kit
202 lines • 35.2 kB
JavaScript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { parallel } from './change-detection';
import { ComponentHarness, HarnessPredicate, } from './component-harness';
/**
* Base harness environment class that can be extended to allow `ComponentHarness`es to be used in
* different test environments (e.g. testbed, protractor, etc.). This class implements the
* functionality of both a `HarnessLoader` and `LocatorFactory`. This class is generic on the raw
* element type, `E`, used by the particular test environment.
*/
export class HarnessEnvironment {
constructor(rawRootElement) {
this.rawRootElement = rawRootElement;
this.rootElement = this.createTestElement(rawRootElement);
}
// Implemented as part of the `LocatorFactory` interface.
documentRootLocatorFactory() {
return this.createEnvironment(this.getDocumentRoot());
}
// Implemented as part of the `LocatorFactory` interface.
locatorFor(...queries) {
return () => _assertResultFound(this._getAllHarnessesAndTestElements(queries), _getDescriptionForLocatorForQueries(queries));
}
// Implemented as part of the `LocatorFactory` interface.
locatorForOptional(...queries) {
return async () => (await this._getAllHarnessesAndTestElements(queries))[0] || null;
}
// Implemented as part of the `LocatorFactory` interface.
locatorForAll(...queries) {
return () => this._getAllHarnessesAndTestElements(queries);
}
// Implemented as part of the `LocatorFactory` interface.
async rootHarnessLoader() {
return this;
}
// Implemented as part of the `LocatorFactory` interface.
async harnessLoaderFor(selector) {
return this.createEnvironment(await _assertResultFound(this.getAllRawElements(selector), [
_getDescriptionForHarnessLoaderQuery(selector),
]));
}
// Implemented as part of the `LocatorFactory` interface.
async harnessLoaderForOptional(selector) {
const elements = await this.getAllRawElements(selector);
return elements[0] ? this.createEnvironment(elements[0]) : null;
}
// Implemented as part of the `LocatorFactory` interface.
async harnessLoaderForAll(selector) {
const elements = await this.getAllRawElements(selector);
return elements.map(element => this.createEnvironment(element));
}
// Implemented as part of the `HarnessLoader` interface.
getHarness(query) {
return this.locatorFor(query)();
}
// Implemented as part of the `HarnessLoader` interface.
getAllHarnesses(query) {
return this.locatorForAll(query)();
}
// Implemented as part of the `HarnessLoader` interface.
async getChildLoader(selector) {
return this.createEnvironment(await _assertResultFound(this.getAllRawElements(selector), [
_getDescriptionForHarnessLoaderQuery(selector),
]));
}
// Implemented as part of the `HarnessLoader` interface.
async getAllChildLoaders(selector) {
return (await this.getAllRawElements(selector)).map(e => this.createEnvironment(e));
}
/** Creates a `ComponentHarness` for the given harness type with the given raw host element. */
createComponentHarness(harnessType, element) {
return new harnessType(this.createEnvironment(element));
}
/**
* Matches the given raw elements with the given list of element and harness queries to produce a
* list of matched harnesses and test elements.
*/
async _getAllHarnessesAndTestElements(queries) {
const { allQueries, harnessQueries, elementQueries, harnessTypes } = _parseQueries(queries);
// Combine all of the queries into one large comma-delimited selector and use it to get all raw
// elements matching any of the individual queries.
const rawElements = await this.getAllRawElements([...elementQueries, ...harnessQueries.map(predicate => predicate.getSelector())].join(','));
// If every query is searching for the same harness subclass, we know every result corresponds
// to an instance of that subclass. Likewise, if every query is for a `TestElement`, we know
// every result corresponds to a `TestElement`. Otherwise we need to verify which result was
// found by which selector so it can be matched to the appropriate instance.
const skipSelectorCheck = (elementQueries.length === 0 && harnessTypes.size === 1) || harnessQueries.length === 0;
const perElementMatches = await parallel(() => rawElements.map(async (rawElement) => {
const testElement = this.createTestElement(rawElement);
const allResultsForElement = await parallel(
// For each query, get `null` if it doesn't match, or a `TestElement` or
// `ComponentHarness` as appropriate if it does match. This gives us everything that
// matches the current raw element, but it may contain duplicate entries (e.g.
// multiple `TestElement` or multiple `ComponentHarness` of the same type).
() => allQueries.map(query => this._getQueryResultForElement(query, rawElement, testElement, skipSelectorCheck)));
return _removeDuplicateQueryResults(allResultsForElement);
}));
return [].concat(...perElementMatches);
}
/**
* Check whether the given query matches the given element, if it does return the matched
* `TestElement` or `ComponentHarness`, if it does not, return null. In cases where the caller
* knows for sure that the query matches the element's selector, `skipSelectorCheck` can be used
* to skip verification and optimize performance.
*/
async _getQueryResultForElement(query, rawElement, testElement, skipSelectorCheck = false) {
if (typeof query === 'string') {
return skipSelectorCheck || (await testElement.matchesSelector(query)) ? testElement : null;
}
if (skipSelectorCheck || (await testElement.matchesSelector(query.getSelector()))) {
const harness = this.createComponentHarness(query.harnessType, rawElement);
return (await query.evaluate(harness)) ? harness : null;
}
return null;
}
}
/**
* Parses a list of queries in the format accepted by the `locatorFor*` methods into an easier to
* work with format.
*/
function _parseQueries(queries) {
const allQueries = [];
const harnessQueries = [];
const elementQueries = [];
const harnessTypes = new Set();
for (const query of queries) {
if (typeof query === 'string') {
allQueries.push(query);
elementQueries.push(query);
}
else {
const predicate = query instanceof HarnessPredicate ? query : new HarnessPredicate(query, {});
allQueries.push(predicate);
harnessQueries.push(predicate);
harnessTypes.add(predicate.harnessType);
}
}
return { allQueries, harnessQueries, elementQueries, harnessTypes };
}
/**
* Removes duplicate query results for a particular element. (e.g. multiple `TestElement`
* instances or multiple instances of the same `ComponentHarness` class.
*/
async function _removeDuplicateQueryResults(results) {
let testElementMatched = false;
let matchedHarnessTypes = new Set();
const dedupedMatches = [];
for (const result of results) {
if (!result) {
continue;
}
if (result instanceof ComponentHarness) {
if (!matchedHarnessTypes.has(result.constructor)) {
matchedHarnessTypes.add(result.constructor);
dedupedMatches.push(result);
}
}
else if (!testElementMatched) {
testElementMatched = true;
dedupedMatches.push(result);
}
}
return dedupedMatches;
}
/** Verifies that there is at least one result in an array. */
async function _assertResultFound(results, queryDescriptions) {
const result = (await results)[0];
if (result == undefined) {
throw Error(`Failed to find element matching one of the following queries:\n` +
queryDescriptions.map(desc => `(${desc})`).join(',\n'));
}
return result;
}
/** Gets a list of description strings from a list of queries. */
function _getDescriptionForLocatorForQueries(queries) {
return queries.map(query => typeof query === 'string'
? _getDescriptionForTestElementQuery(query)
: _getDescriptionForComponentHarnessQuery(query));
}
/** Gets a description string for a `ComponentHarness` query. */
function _getDescriptionForComponentHarnessQuery(query) {
const harnessPredicate = query instanceof HarnessPredicate ? query : new HarnessPredicate(query, {});
const { name, hostSelector } = harnessPredicate.harnessType;
const description = `${name} with host element matching selector: "${hostSelector}"`;
const constraints = harnessPredicate.getDescription();
return (description +
(constraints ? ` satisfying the constraints: ${harnessPredicate.getDescription()}` : ''));
}
/** Gets a description string for a `TestElement` query. */
function _getDescriptionForTestElementQuery(selector) {
return `TestElement for element matching selector: "${selector}"`;
}
/** Gets a description string for a `HarnessLoader` query. */
function _getDescriptionForHarnessLoaderQuery(selector) {
return `HarnessLoader for element matching selector: "${selector}"`;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFybmVzcy1lbnZpcm9ubWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9jZGsvdGVzdGluZy9oYXJuZXNzLWVudmlyb25tZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxRQUFRLEVBQUMsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBRUwsZ0JBQWdCLEVBR2hCLGdCQUFnQixHQUlqQixNQUFNLHFCQUFxQixDQUFDO0FBcUI3Qjs7Ozs7R0FLRztBQUNILE1BQU0sT0FBZ0Isa0JBQWtCO0lBSXRDLFlBQWdDLGNBQWlCO1FBQWpCLG1CQUFjLEdBQWQsY0FBYyxDQUFHO1FBQy9DLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLGlCQUFpQixDQUFDLGNBQWMsQ0FBQyxDQUFDO0lBQzVELENBQUM7SUFFRCx5REFBeUQ7SUFDekQsMEJBQTBCO1FBQ3hCLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQyxDQUFDO0lBQ3hELENBQUM7SUFFRCx5REFBeUQ7SUFDekQsVUFBVSxDQUNSLEdBQUcsT0FBVTtRQUViLE9BQU8sR0FBRyxFQUFFLENBQ1Ysa0JBQWtCLENBQ2hCLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxPQUFPLENBQUMsRUFDN0MsbUNBQW1DLENBQUMsT0FBTyxDQUFDLENBQzdDLENBQUM7SUFDTixDQUFDO0lBRUQseURBQXlEO0lBQ3pELGtCQUFrQixDQUNoQixHQUFHLE9BQVU7UUFFYixPQUFPLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQztJQUN0RixDQUFDO0lBRUQseURBQXlEO0lBQ3pELGFBQWEsQ0FDWCxHQUFHLE9BQVU7UUFFYixPQUFPLEdBQUcsRUFB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QWlELFFBQVEsR0FBRyxDQUFDO0FBQ3RFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgR29vZ2xlIExMQyBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtwYXJhbGxlbH0gZnJvbSAnLi9jaGFuZ2UtZGV0ZWN0aW9uJztcbmltcG9ydCB7XG4gIEFzeW5jRmFjdG9yeUZuLFxuICBDb21wb25lbnRIYXJuZXNzLFxuICBDb21wb25lbnRIYXJuZXNzQ29uc3RydWN0b3IsXG4gIEhhcm5lc3NMb2FkZXIsXG4gIEhhcm5lc3NQcmVkaWNhdGUsXG4gIEhhcm5lc3NRdWVyeSxcbiAgTG9jYXRvckZhY3RvcnksXG4gIExvY2F0b3JGblJlc3VsdCxcbn0gZnJvbSAnLi9jb21wb25lbnQtaGFybmVzcyc7XG5pbXBvcnQge1Rlc3RFbGVtZW50fSBmcm9tICcuL3Rlc3QtZWxlbWVudCc7XG5cbi8qKiBQYXJzZWQgZm9ybSBvZiB0aGUgcXVlcmllcyBwYXNzZWQgdG8gdGhlIGBsb2NhdG9yRm9yKmAgbWV0aG9kcy4gKi9cbnR5cGUgUGFyc2VkUXVlcmllczxUIGV4dGVuZHMgQ29tcG9uZW50SGFybmVzcz4gPSB7XG4gIC8qKiBUaGUgZnVsbCBsaXN0IG9mIHF1ZXJpZXMsIGluIHRoZWlyIG9yaWdpbmFsIG9yZGVyLiAqL1xuICBhbGxRdWVyaWVzOiAoc3RyaW5nIHwgSGFybmVzc1ByZWRpY2F0ZTxUPilbXTtcbiAgLyoqXG4gICAqIEEgZmlsdGVyZWQgdmlldyBvZiBgYWxsUXVlcmllc2AgY29udGFpbmluZyBvbmx5IHRoZSBxdWVyaWVzIHRoYXQgYXJlIGxvb2tpbmcgZm9yIGFcbiAgICogYENvbXBvbmVudEhhcm5lc3NgXG4gICAqL1xuICBoYXJuZXNzUXVlcmllczogSGFybmVzc1ByZWRpY2F0ZTxUPltdO1xuICAvKipcbiAgICogQSBmaWx0ZXJlZCB2aWV3IG9mIGBhbGxRdWVyaWVzYCBjb250YWluaW5nIG9ubHkgdGhlIHF1ZXJpZXMgdGhhdCBhcmUgbG9va2luZyBmb3IgYVxuICAgKiBgVGVzdEVsZW1lbnRgXG4gICAqL1xuICBlbGVtZW50UXVlcmllczogc3RyaW5nW107XG4gIC8qKiBUaGUgc2V0IG9mIGFsbCBgQ29tcG9uZW50SGFybmVzc2Agc3ViY2xhc3NlcyByZXByZXNlbnRlZCBpbiB0aGUgb3JpZ2luYWwgcXVlcnkgbGlzdC4gKi9cbiAgaGFybmVzc1R5cGVzOiBTZXQ8Q29tcG9uZW50SGFybmVzc0NvbnN0cnVjdG9yPFQ+Pjtcbn07XG5cbi8qKlxuICogQmFzZSBoYXJuZXNzIGVudmlyb25tZW50IGNsYXNzIHRoYXQgY2FuIGJlIGV4dGVuZGVkIHRvIGFsbG93IGBDb21wb25lbnRIYXJuZXNzYGVzIHRvIGJlIHVzZWQgaW5cbiAqIGRpZmZlcmVudCB0ZXN0IGVudmlyb25tZW50cyAoZS5nLiB0ZXN0YmVkLCBwcm90cmFjdG9yLCBldGMuKS4gVGhpcyBjbGFzcyBpbXBsZW1lbnRzIHRoZVxuICogZnVuY3Rpb25hbGl0eSBvZiBib3RoIGEgYEhhcm5lc3NMb2FkZXJgIGFuZCBgTG9jYXRvckZhY3RvcnlgLiBUaGlzIGNsYXNzIGlzIGdlbmVyaWMgb24gdGhlIHJhd1xuICogZWxlbWVudCB0eXBlLCBgRWAsIHVzZWQgYnkgdGhlIHBhcnRpY3VsYXIgdGVzdCBlbnZpcm9ubWVudC5cbiAqL1xuZXhwb3J0IGFic3RyYWN0IGNsYXNzIEhhcm5lc3NFbnZpcm9ubWVudDxFPiBpbXBsZW1lbnRzIEhhcm5lc3NMb2FkZXIsIExvY2F0b3JGYWN0b3J5IHtcbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIHJvb3RFbGVtZW50OiBUZXN0RWxlbWVudDtcblxuICBwcm90ZWN0ZWQgY29uc3RydWN0b3IocHJvdGVjdGVkIHJhd1Jvb3RFbGVtZW50OiBFKSB7XG4gICAgdGhpcy5yb290RWxlbWVudCA9IHRoaXMuY3JlYXRlVGVzdEVsZW1lbnQocmF3Um9vdEVsZW1lbnQpO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGRvY3VtZW50Um9vdExvY2F0b3JGYWN0b3J5KCk6IExvY2F0b3JGYWN0b3J5IHtcbiAgICByZXR1cm4gdGhpcy5jcmVhdGVFbnZpcm9ubWVudCh0aGlzLmdldERvY3VtZW50Um9vdCgpKTtcbiAgfVxuXG4gIC8vIEltcGxlbWVudGVkIGFzIHBhcnQgb2YgdGhlIGBMb2NhdG9yRmFjdG9yeWAgaW50ZXJmYWNlLlxuICBsb2NhdG9yRm9yPFQgZXh0ZW5kcyAoSGFybmVzc1F1ZXJ5PGFueT4gfCBzdHJpbmcpW10+KFxuICAgIC4uLnF1ZXJpZXM6IFRcbiAgKTogQXN5bmNGYWN0b3J5Rm48TG9jYXRvckZuUmVzdWx0PFQ+PiB7XG4gICAgcmV0dXJuICgpID0+XG4gICAgICBfYXNzZXJ0UmVzdWx0Rm91bmQoXG4gICAgICAgIHRoaXMuX2dldEFsbEhhcm5lc3Nlc0FuZFRlc3RFbGVtZW50cyhxdWVyaWVzKSxcbiAgICAgICAgX2dldERlc2NyaXB0aW9uRm9yTG9jYXRvckZvclF1ZXJpZXMocXVlcmllcyksXG4gICAgICApO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGxvY2F0b3JGb3JPcHRpb25hbDxUIGV4dGVuZHMgKEhhcm5lc3NRdWVyeTxhbnk+IHwgc3RyaW5nKVtdPihcbiAgICAuLi5xdWVyaWVzOiBUXG4gICk6IEFzeW5jRmFjdG9yeUZuPExvY2F0b3JGblJlc3VsdDxUPiB8IG51bGw+IHtcbiAgICByZXR1cm4gYXN5bmMgKCkgPT4gKGF3YWl0IHRoaXMuX2dldEFsbEhhcm5lc3Nlc0FuZFRlc3RFbGVtZW50cyhxdWVyaWVzKSlbMF0gfHwgbnVsbDtcbiAgfVxuXG4gIC8vIEltcGxlbWVudGVkIGFzIHBhcnQgb2YgdGhlIGBMb2NhdG9yRmFjdG9yeWAgaW50ZXJmYWNlLlxuICBsb2NhdG9yRm9yQWxsPFQgZXh0ZW5kcyAoSGFybmVzc1F1ZXJ5PGFueT4gfCBzdHJpbmcpW10+KFxuICAgIC4uLnF1ZXJpZXM6IFRcbiAgKTogQXN5bmNGYWN0b3J5Rm48TG9jYXRvckZuUmVzdWx0PFQ+W10+IHtcbiAgICByZXR1cm4gKCkgPT4gdGhpcy5fZ2V0QWxsSGFybmVzc2VzQW5kVGVzdEVsZW1lbnRzKHF1ZXJpZXMpO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGFzeW5jIHJvb3RIYXJuZXNzTG9hZGVyKCk6IFByb21pc2U8SGFybmVzc0xvYWRlcj4ge1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGFzeW5jIGhhcm5lc3NMb2FkZXJGb3Ioc2VsZWN0b3I6IHN0cmluZyk6IFByb21pc2U8SGFybmVzc0xvYWRlcj4ge1xuICAgIHJldHVybiB0aGlzLmNyZWF0ZUVudmlyb25tZW50KFxuICAgICAgYXdhaXQgX2Fzc2VydFJlc3VsdEZvdW5kKHRoaXMuZ2V0QWxsUmF3RWxlbWVudHMoc2VsZWN0b3IpLCBbXG4gICAgICAgIF9nZXREZXNjcmlwdGlvbkZvckhhcm5lc3NMb2FkZXJRdWVyeShzZWxlY3RvciksXG4gICAgICBdKSxcbiAgICApO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGFzeW5jIGhhcm5lc3NMb2FkZXJGb3JPcHRpb25hbChzZWxlY3Rvcjogc3RyaW5nKTogUHJvbWlzZTxIYXJuZXNzTG9hZGVyIHwgbnVsbD4ge1xuICAgIGNvbnN0IGVsZW1lbnRzID0gYXdhaXQgdGhpcy5nZXRBbGxSYXdFbGVtZW50cyhzZWxlY3Rvcik7XG4gICAgcmV0dXJuIGVsZW1lbnRzWzBdID8gdGhpcy5jcmVhdGVFbnZpcm9ubWVudChlbGVtZW50c1swXSkgOiBudWxsO1xuICB9XG5cbiAgLy8gSW1wbGVtZW50ZWQgYXMgcGFydCBvZiB0aGUgYExvY2F0b3JGYWN0b3J5YCBpbnRlcmZhY2UuXG4gIGFzeW5jIGhhcm5lc3NMb2FkZXJGb3JBbGwoc2VsZWN0b3I6IHN0cmluZyk6IFByb21pc2U8SGFybmVzc0xvYWRlcltdPiB7XG4gICAgY29uc3QgZWxlbWVudHMgPSBhd2FpdCB0aGlzLmdldEFsbFJhd0VsZW1lbnRzKHNlbGVjdG9yKTtcbiAgICByZXR1cm4gZWxlbWVudHMubWFwKGVsZW1lbnQgPT4gdGhpcy5jcmVhdGVFbnZpcm9ubWVudChlbGVtZW50KSk7XG4gIH1cblxuICAvLyBJbXBsZW1lbnRlZCBhcyBwYXJ0IG9mIHRoZSBgSGFybmVzc0xvYWRlcmAgaW50ZXJmYWNlLlxuICBnZXRIYXJuZXNzPFQgZXh0ZW5kcyBDb21wb25lbnRIYXJuZXNzPihxdWVyeTogSGFybmVzc1F1ZXJ5PFQ+KTogUHJvbWlzZTxUPiB7XG4gICAgcmV0dXJuIHRoaXMubG9jYXRvckZvcihxdWVyeSkoKTtcbiAgfVxuXG4gIC8vIEltcGxlbWVudGVkIGFzIHBhcnQgb2YgdGhlIGBIYXJuZXNzTG9hZGVyYCBpbnRlcmZhY2UuXG4gIGdldEFsbEhhcm5lc3NlczxUIGV4dGVuZHMgQ29tcG9uZW50SGFybmVzcz4ocXVlcnk6IEhhcm5lc3NRdWVyeTxUPik6IFByb21pc2U8VFtdPiB7XG4gICAgcmV0dXJuIHRoaXMubG9jYXRvckZvckFsbChxdWVyeSkoKTtcbiAgfVxuXG4gIC8vIEltcGxlbWVudGVkIGFzIHBhcnQgb2YgdGhlIGBIYXJuZXNzTG9hZGVyYCBpbnRlcmZhY2UuXG4gIGFzeW5jIGdldENoaWxkTG9hZGVyKHNlbGVjdG9yOiBzdHJpbmcpOiBQcm9taXNlPEhhcm5lc3NMb2FkZXI+IHtcbiAgICByZXR1cm4gdGhpcy5jcmVhdGVFbnZpcm9ubWVudChcbiAgICAgIGF3YWl0IF9hc3NlcnRSZXN1bHRGb3VuZCh0aGlzLmdldEFsbFJhd0VsZW1lbnRzKHNlbGVjdG9yKSwgW1xuICAgICAgICBfZ2V0RGVzY3JpcHRpb25Gb3JIYXJuZXNzTG9hZGVyUXVlcnkoc2VsZWN0b3IpLFxuICAgICAgXSksXG4gICAgKTtcbiAgfVxuXG4gIC8vIEltcGxlbWVudGVkIGFzIHBhcnQgb2YgdGhlIGBIYXJuZXNzTG9hZGVyYCBpbnRlcmZhY2UuXG4gIGFzeW5jIGdldEFsbENoaWxkTG9hZGVycyhzZWxlY3Rvcjogc3RyaW5nKTogUHJvbWlzZTxIYXJuZXNzTG9hZGVyW10+IHtcbiAgICByZXR1cm4gKGF3YWl0IHRoaXMuZ2V0QWxsUmF3RWxlbWVudHMoc2VsZWN0b3IpKS5tYXAoZSA9PiB0aGlzLmNyZWF0ZUVudmlyb25tZW50KGUpKTtcbiAgfVxuXG4gIC8qKiBDcmVhdGVzIGEgYENvbXBvbmVudEhhcm5lc3NgIGZvciB0aGUgZ2l2ZW4gaGFybmVzcyB0eXBlIHdpdGggdGhlIGdpdmVuIHJhdyBob3N0IGVsZW1lbnQuICovXG4gIHByb3RlY3RlZCBjcmVhdGVDb21wb25lbnRIYXJuZXNzPFQgZXh0ZW5kcyBDb21wb25lbnRIYXJuZXNzPihcbiAgICBoYXJuZXNzVHlwZTogQ29tcG9uZW50SGFybmVzc0NvbnN0cnVjdG9yPFQ+LFxuICAgIGVsZW1lbnQ6IEUsXG4gICk6IFQge1xuICAgIHJldHVybiBuZXcgaGFybmVzc1R5cGUodGhpcy5jcmVhdGVFbnZpcm9ubWVudChlbGVtZW50KSk7XG4gIH1cblxuICAvLyBQYXJ0IG9mIExvY2F0b3JGYWN0b3J5IGludGVyZmFjZSwgc3ViY2xhc3NlcyB3aWxsIGltcGxlbWVudC5cbiAgYWJzdHJhY3QgZm9yY2VTdGFiaWxpemUoKTogUHJvbWlzZTx2b2lkPjtcblxuICAvLyBQYXJ0IG9mIExvY2F0b3JGYWN0b3J5IGludGVyZmFjZSwgc3ViY2xhc3NlcyB3aWxsIGltcGxlbWVudC5cbiAgYWJzdHJhY3Qgd2FpdEZvclRhc2tzT3V0c2lkZUFuZ3VsYXIoKTogUHJvbWlzZTx2b2lkPjtcblxuICAvKiogR2V0cyB0aGUgcm9vdCBlbGVtZW50IGZvciB0aGUgZG9jdW1lbnQuICovXG4gIHByb3RlY3RlZCBhYnN0cmFjdCBnZXREb2N1bWVudFJvb3QoKTogRTtcblxuICAvKiogQ3JlYXRlcyBhIGBUZXN0RWxlbWVudGAgZnJvbSBhIHJhdyBlbGVtZW50LiAqL1xuICBwcm90ZWN0ZWQgYWJzdHJhY3QgY3JlYXRlVGVzdEVsZW1lbnQoZWxlbWVudDogRSk6IFRlc3RFbGVtZW50O1xuXG4gIC8qKiBDcmVhdGVzIGEgYEhhcm5lc3NMb2FkZXJgIHJvb3RlZCBhdCB0aGUgZ2l2ZW4gcmF3IGVsZW1lbnQuICovXG4gIHByb3RlY3RlZCBhYnN0cmFjdCBjcmVhdGVFbnZpcm9ubWVudChlbGVtZW50OiBFKTogSGFybmVzc0Vudmlyb25tZW50PEU+O1xuXG4gIC8qKlxuICAgKiBHZXRzIGEgbGlzdCBvZiBhbGwgZWxlbWVudHMgbWF0Y2hpbmcgdGhlIGdpdmVuIHNlbGVjdG9yIHVuZGVyIHRoaXMgZW52aXJvbm1lbnQncyByb290IGVsZW1lbnQuXG4gICAqL1xuICBwcm90ZWN0ZWQgYWJzdHJhY3QgZ2V0QWxsUmF3RWxlbWVudHMoc2VsZWN0b3I6IHN0cmluZyk6IFByb21pc2U8RVtdPjtcblxuICAvKipcbiAgICogTWF0Y2hlcyB0aGUgZ2l2ZW4gcmF3IGVsZW1lbnRzIHdpdGggdGhlIGdpdmVuIGxpc3Qgb2YgZWxlbWVudCBhbmQgaGFybmVzcyBxdWVyaWVzIHRvIHByb2R1Y2UgYVxuICAgKiBsaXN0IG9mIG1hdGNoZWQgaGFybmVzc2VzIGFuZCB0ZXN0IGVsZW1lbnRzLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyBfZ2V0QWxsSGFybmVzc2VzQW5kVGVzdEVsZW1lbnRzPFQgZXh0ZW5kcyAoSGFybmVzc1F1ZXJ5PGFueT4gfCBzdHJpbmcpW10+KFxuICAgIHF1ZXJpZXM6IFQsXG4gICk6IFByb21pc2U8TG9jYXRvckZuUmVzdWx0PFQ+W10+IHtcbiAgICBjb25zdCB7YWxsUXVlcmllcywgaGFybmVzc1F1ZXJpZXMsIGVsZW1lbnRRdWVyaWVzLCBoYXJuZXNzVHlwZXN9ID0gX3BhcnNlUXVlcmllcyhxdWVyaWVzKTtcblxuICAgIC8vIENvbWJpbmUgYWxsIG9mIHRoZSBxdWVyaWVzIGludG8gb25lIGxhcmdlIGNvbW1hLWRlbGltaXRlZCBzZWxlY3RvciBhbmQgdXNlIGl0IHRvIGdldCBhbGwgcmF3XG4gICAgLy8gZWxlbWVudHMgbWF0Y2hpbmcgYW55IG9mIHRoZSBpbmRpdmlkdWFsIHF1ZXJpZXMuXG4gICAgY29uc3QgcmF3RWxlbWVudHMgPSBhd2FpdCB0aGlzLmdldEFsbFJhd0VsZW1lbnRzKFxuICAgICAgWy4uLmVsZW1lbnRRdWVyaWVzLCAuLi5oYXJuZXNzUXVlcmllcy5tYXAocHJlZGljYXRlID0+IHByZWRpY2F0ZS5nZXRTZWxlY3RvcigpKV0uam9pbignLCcpLFxuICAgICk7XG5cbiAgICAvLyBJZiBldmVyeSBxdWVyeSBpcyBzZWFyY2hpbmcgZm9yIHRoZSBzYW1lIGhhcm5lc3Mgc3ViY2xhc3MsIHdlIGtub3cgZXZlcnkgcmVzdWx0IGNvcnJlc3BvbmRzXG4gICAgLy8gdG8gYW4gaW5zdGFuY2Ugb2YgdGhhdCBzdWJjbGFzcy4gTGlrZXdpc2UsIGlmIGV2ZXJ5IHF1ZXJ5IGlzIGZvciBhIGBUZXN0RWxlbWVudGAsIHdlIGtub3dcbiAgICAvLyBldmVyeSByZXN1bHQgY29ycmVzcG9uZHMgdG8gYSBgVGVzdEVsZW1lbnRgLiBPdGhlcndpc2Ugd2UgbmVlZCB0byB2ZXJpZnkgd2hpY2ggcmVzdWx0IHdhc1xuICAgIC8vIGZvdW5kIGJ5IHdoaWNoIHNlbGVjdG9yIHNvIGl0IGNhbiBiZSBtYXRjaGVkIHRvIHRoZSBhcHByb3ByaWF0ZSBpbnN0YW5jZS5cbiAgICBjb25zdCBza2lwU2VsZWN0b3JDaGVjayA9XG4gICAgICAoZWxlbWVudFF1ZXJpZXMubGVuZ3RoID09PSAwICYmIGhhcm5lc3NUeXBlcy5zaXplID09PSAxKSB8fCBoYXJuZXNzUXVlcmllcy5sZW5ndGggPT09IDA7XG5cbiAgICBjb25zdCBwZXJFbGVtZW50TWF0Y2hlcyA9IGF3YWl0IHBhcmFsbGVsKCgpID0+XG4gICAgICByYXdFbGVtZW50cy5tYXAoYXN5bmMgcmF3RWxlbWVudCA9PiB7XG4gICAgICAgIGNvbnN0IHRlc3RFbGVtZW50ID0gdGhpcy5jcmVhdGVUZXN0RWxlbWVudChyYXdFbGVtZW50KTtcbiAgICAgICAgY29uc3QgYWxsUmVzdWx0c0ZvckVsZW1lbnQgPSBhd2FpdCBwYXJhbGxlbChcbiAgICAgICAgICAvLyBGb3IgZWFjaCBxdWVyeSwgZ2V0IGBudWxsYCBpZiBpdCBkb2Vzbid0IG1hdGNoLCBvciBhIGBUZXN0RWxlbWVudGAgb3JcbiAgICAgICAgICAvLyBgQ29tcG9uZW50SGFybmVzc2AgYXMgYXBwcm9wcmlhdGUgaWYgaXQgZG9lcyBtYXRjaC4gVGhpcyBnaXZlcyB1cyBldmVyeXRoaW5nIHRoYXRcbiAgICAgICAgICAvLyBtYXRjaGVzIHRoZSBjdXJyZW50IHJhdyBlbGVtZW50LCBidXQgaXQgbWF5IGNvbnRhaW4gZHVwbGljYXRlIGVudHJpZXMgKGUuZy5cbiAgICAgICAgICAvLyBtdWx0aXBsZSBgVGVzdEVsZW1lbnRgIG9yIG11bHRpcGxlIGBDb21wb25lbnRIYXJuZXNzYCBvZiB0aGUgc2FtZSB0eXBlKS5cbiAgICAgICAgICAoKSA9PlxuICAgICAgICAgICAgYWxsUXVlcmllcy5tYXAocXVlcnkgPT5cbiAgICAgICAgICAgICAgdGhpcy5fZ2V0UXVlcnlSZXN1bHRGb3JFbGVtZW50KHF1ZXJ5LCByYXdFbGVtZW50LCB0ZXN0RWxlbWVudCwgc2tpcFNlbGVjdG9yQ2hlY2spLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuIF9yZW1vdmVEdXBsaWNhdGVRdWVyeVJlc3VsdHMoYWxsUmVzdWx0c0ZvckVsZW1lbnQpO1xuICAgICAgfSksXG4gICAgKTtcbiAgICByZXR1cm4gKFtdIGFzIGFueSkuY29uY2F0KC4uLnBlckVsZW1lbnRNYXRjaGVzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayB3aGV0aGVyIHRoZSBnaXZlbiBxdWVyeSBtYXRjaGVzIHRoZSBnaXZlbiBlbGVtZW50LCBpZiBpdCBkb2VzIHJldHVybiB0aGUgbWF0Y2hlZFxuICAgKiBgVGVzdEVsZW1lbnRgIG9yIGBDb21wb25lbnRIYXJuZXNzYCwgaWYgaXQgZG9lcyBub3QsIHJldHVybiBudWxsLiBJbiBjYXNlcyB3aGVyZSB0aGUgY2FsbGVyXG4gICAqIGtub3dzIGZvciBzdXJlIHRoYXQgdGhlIHF1ZXJ5IG1hdGNoZXMgdGhlIGVsZW1lbnQncyBzZWxlY3RvciwgYHNraXBTZWxlY3RvckNoZWNrYCBjYW4gYmUgdXNlZFxuICAgKiB0byBza2lwIHZlcmlmaWNhdGlvbiBhbmQgb3B0aW1pemUgcGVyZm9ybWFuY2UuXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIF9nZXRRdWVyeVJlc3VsdEZvckVsZW1lbnQ8VCBleHRlbmRzIENvbXBvbmVudEhhcm5lc3M+KFxuICAgIHF1ZXJ5OiBzdHJpbmcgfCBIYXJuZXNzUHJlZGljYXRlPFQ+LFxuICAgIHJhd0VsZW1lbnQ6IEUsXG4gICAgdGVzdEVsZW1lbnQ6IFRlc3RFbGVtZW50LFxuICAgIHNraXBTZWxlY3RvckNoZWNrOiBib29sZWFuID0gZmFsc2UsXG4gICk6IFByb21pc2U8VCB8IFRlc3RFbGVtZW50IHwgbnVsbD4ge1xuICAgIGlmICh0eXBlb2YgcXVlcnkgPT09ICdzdHJpbmcnKSB7XG4gICAgICByZXR1cm4gc2tpcFNlbGVjdG9yQ2hlY2sgfHwgKGF3YWl0IHRlc3RFbGVtZW50Lm1hdGNoZXNTZWxlY3RvcihxdWVyeSkpID8gdGVzdEVsZW1lbnQgOiBudWxsO1xuICAgIH1cbiAgICBpZiAoc2tpcFNlbGVjdG9yQ2hlY2sgfHwgKGF3YWl0IHRlc3RFbGVtZW50Lm1hdGNoZXNTZWxlY3RvcihxdWVyeS5nZXRTZWxlY3RvcigpKSkpIHtcbiAgICAgIGNvbnN0IGhhcm5lc3MgPSB0aGlzLmNyZWF0ZUNvbXBvbmVudEhhcm5lc3MocXVlcnkuaGFybmVzc1R5cGUsIHJhd0VsZW1lbnQpO1xuICAgICAgcmV0dXJuIChhd2FpdCBxdWVyeS5ldmFsdWF0ZShoYXJuZXNzKSkgPyBoYXJuZXNzIDogbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuLyoqXG4gKiBQYXJzZXMgYSBsaXN0IG9mIHF1ZXJpZXMgaW4gdGhlIGZvcm1hdCBhY2NlcHRlZCBieSB0aGUgYGxvY2F0b3JGb3IqYCBtZXRob2RzIGludG8gYW4gZWFzaWVyIHRvXG4gKiB3b3JrIHdpdGggZm9ybWF0LlxuICovXG5mdW5jdGlvbiBfcGFyc2VRdWVyaWVzPFQgZXh0ZW5kcyAoSGFybmVzc1F1ZXJ5PGFueT4gfCBzdHJpbmcpW10+KFxuICBxdWVyaWVzOiBULFxuKTogUGFyc2VkUXVlcmllczxMb2NhdG9yRm5SZXN1bHQ8VD4gJiBDb21wb25lbnRIYXJuZXNzPiB7XG4gIGNvbnN0IGFsbFF1ZXJpZXMgPSBbXTtcbiAgY29uc3QgaGFybmVzc1F1ZXJpZXMgPSBbXTtcbiAgY29uc3QgZWxlbWVudFF1ZXJpZXMgPSBbXTtcbiAgY29uc3QgaGFybmVzc1R5cGVzID0gbmV3IFNldDxcbiAgICBDb21wb25lbnRIYXJuZXNzQ29uc3RydWN0b3I8TG9jYXRvckZuUmVzdWx0PFQ+ICYgQ29tcG9uZW50SGFybmVzcz5cbiAgPigpO1xuXG4gIGZvciAoY29uc3QgcXVlcnkgb2YgcXVlcmllcykge1xuICAgIGlmICh0eXBlb2YgcXVlcnkgPT09ICdzdHJpbmcnKSB7XG4gICAgICBhbGxRdWVyaWVzLnB1c2gocXVlcnkpO1xuICAgICAgZWxlbWVudFF1ZXJpZXMucHVzaChxdWVyeSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnN0IHByZWRpY2F0ZSA9IHF1ZXJ5IGluc3RhbmNlb2YgSGFybmVzc1ByZWRpY2F0ZSA/IHF1ZXJ5IDogbmV3IEhhcm5lc3NQcmVkaWNhdGUocXVlcnksIHt9KTtcbiAgICAgIGFsbFF1ZXJpZXMucHVzaChwcmVkaWNhdGUpO1xuICAgICAgaGFybmVzc1F1ZXJpZXMucHVzaChwcmVkaWNhdGUpO1xuICAgICAgaGFybmVzc1R5cGVzLmFkZChwcmVkaWNhdGUuaGFybmVzc1R5cGUpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiB7YWxsUXVlcmllcywgaGFybmVzc1F1ZXJpZXMsIGVsZW1lbnRRdWVyaWVzLCBoYXJuZXNzVHlwZXN9O1xufVxuXG4vKipcbiAqIFJlbW92ZXMgZHVwbGljYXRlIHF1ZXJ5IHJlc3VsdHMgZm9yIGEgcGFydGljdWxhciBlbGVtZW50LiAoZS5nLiBtdWx0aXBsZSBgVGVzdEVsZW1lbnRgXG4gKiBpbnN0YW5jZXMgb3IgbXVsdGlwbGUgaW5zdGFuY2VzIG9mIHRoZSBzYW1lIGBDb21wb25lbnRIYXJuZXNzYCBjbGFzcy5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gX3JlbW92ZUR1cGxpY2F0ZVF1ZXJ5UmVzdWx0czxUIGV4dGVuZHMgKENvbXBvbmVudEhhcm5lc3MgfCBUZXN0RWxlbWVudCB8IG51bGwpW10+KFxuICByZXN1bHRzOiBULFxuKTogUHJvbWlzZTxUPiB7XG4gIGxldCB0ZXN0RWxlbWVudE1hdGNoZWQgPSBmYWxzZTtcbiAgbGV0IG1hdGNoZWRIYXJuZXNzVHlwZXMgPSBuZXcgU2V0KCk7XG4gIGNvbnN0IGRlZHVwZWRNYXRjaGVzID0gW107XG4gIGZvciAoY29uc3QgcmVzdWx0IG9mIHJlc3VsdHMpIHtcbiAgICBpZiAoIXJlc3VsdCkge1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGlmIChyZXN1bHQgaW5zdGFuY2VvZiBDb21wb25lbnRIYXJuZXNzKSB7XG4gICAgICBpZiAoIW1hdGNoZWRIYXJuZXNzVHlwZXMuaGFzKHJlc3VsdC5jb25zdHJ1Y3RvcikpIHtcbiAgICAgICAgbWF0Y2hlZEhhcm5lc3NUeXBlcy5hZGQocmVzdWx0LmNvbnN0cnVjdG9yKTtcbiAgICAgICAgZGVkdXBlZE1hdGNoZXMucHVzaChyZXN1bHQpO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoIXRlc3RFbGVtZW50TWF0Y2hlZCkge1xuICAgICAgdGVzdEVsZW1lbnRNYXRjaGVkID0gdHJ1ZTtcbiAgICAgIGRlZHVwZWRNYXRjaGVzLnB1c2gocmVzdWx0KTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGRlZHVwZWRNYXRjaGVzIGFzIFQ7XG59XG5cbi8qKiBWZXJpZmllcyB0aGF0IHRoZXJlIGlzIGF0IGxlYXN0IG9uZSByZXN1bHQgaW4gYW4gYXJyYXkuICovXG5hc3luYyBmdW5jdGlvbiBfYXNzZXJ0UmVzdWx0Rm91bmQ8VD4oXG4gIHJlc3VsdHM6IFByb21pc2U8VFtdPixcbiAgcXVlcnlEZXNjcmlwdGlvbnM6IHN0cmluZ1tdLFxuKTogUHJvbWlzZTxUPiB7XG4gIGNvbnN0IHJlc3VsdCA9IChhd2FpdCByZXN1bHRzKVswXTtcbiAgaWYgKHJlc3VsdCA9PSB1bmRlZmluZWQpIHtcbiAgICB0aHJvdyBFcnJvcihcbiAgICAgIGBGYWlsZWQgdG8gZmluZCBlbGVtZW50IG1hdGNoaW5nIG9uZSBvZiB0aGUgZm9sbG93aW5nIHF1ZXJpZXM6XFxuYCArXG4gICAgICAgIHF1ZXJ5RGVzY3JpcHRpb25zLm1hcChkZXNjID0+IGAoJHtkZXNjfSlgKS5qb2luKCcsXFxuJyksXG4gICAgKTtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuXG4vKiogR2V0cyBhIGxpc3Qgb2YgZGVzY3JpcHRpb24gc3RyaW5ncyBmcm9tIGEgbGlzdCBvZiBxdWVyaWVzLiAqL1xuZnVuY3Rpb24gX2dldERlc2NyaXB0aW9uRm9yTG9jYXRvckZvclF1ZXJpZXMocXVlcmllczogKHN0cmluZyB8IEhhcm5lc3NRdWVyeTxhbnk+KVtdKSB7XG4gIHJldHVybiBxdWVyaWVzLm1hcChxdWVyeSA9PlxuICAgIHR5cGVvZiBxdWVyeSA9PT0gJ3N0cmluZydcbiAgICAgID8gX2dldERlc2NyaXB0aW9uRm9yVGVzdEVsZW1lbnRRdWVyeShxdWVyeSlcbiAgICAgIDogX2dldERlc2NyaXB0aW9uRm9yQ29tcG9uZW50SGFybmVzc1F1ZXJ5KHF1ZXJ5KSxcbiAgKTtcbn1cblxuLyoqIEdldHMgYSBkZXNjcmlwdGlvbiBzdHJpbmcgZm9yIGEgYENvbXBvbmVudEhhcm5lc3NgIHF1ZXJ5LiAqL1xuZnVuY3Rpb24gX2dldERlc2NyaXB0aW9uRm9yQ29tcG9uZW50SGFybmVzc1F1ZXJ5KHF1ZXJ5OiBIYXJuZXNzUXVlcnk8YW55Pikge1xuICBjb25zdCBoYXJuZXNzUHJlZGljYXRlID1cbiAgICBxdWVyeSBpbnN0YW5jZW9mIEhhcm5lc3NQcmVkaWNhdGUgPyBxdWVyeSA6IG5ldyBIYXJuZXNzUHJlZGljYXRlKHF1ZXJ5LCB7fSk7XG4gIGNvbnN0IHtuYW1lLCBob3N0U2VsZWN0b3J9ID0gaGFybmVzc1ByZWRpY2F0ZS5oYXJuZXNzVHlwZTtcbiAgY29uc3QgZGVzY3JpcHRpb24gPSBgJHtuYW1lfSB3aXRoIGhvc3QgZWxlbWVudCBtYXRjaGluZyBzZWxlY3RvcjogXCIke2hvc3RTZWxlY3Rvcn1cImA7XG4gIGNvbnN0IGNvbnN0cmFpbnRzID0gaGFybmVzc1ByZWRpY2F0ZS5nZXREZXNjcmlwdGlvbigpO1xuICByZXR1cm4gKFxuICAgIGRlc2NyaXB0aW9uICtcbiAgICAoY29uc3RyYWludHMgPyBgIHNhdGlzZnlpbmcgdGhlIGNvbnN0cmFpbnRzOiAke2hhcm5lc3NQcmVkaWNhdGUuZ2V0RGVzY3JpcHRpb24oKX1gIDogJycpXG4gICk7XG59XG5cbi8qKiBHZXRzIGEgZGVzY3JpcHRpb24gc3RyaW5nIGZvciBhIGBUZXN0RWxlbWVudGAgcXVlcnkuICovXG5mdW5jdGlvbiBfZ2V0RGVzY3JpcHRpb25Gb3JUZXN0RWxlbWVudFF1ZXJ5KHNlbGVjdG9yOiBzdHJpbmcpIHtcbiAgcmV0dXJuIGBUZXN0RWxlbWVudCBmb3IgZWxlbWVudCBtYXRjaGluZyBzZWxlY3RvcjogXCIke3NlbGVjdG9yfVwiYDtcbn1cblxuLyoqIEdldHMgYSBkZXNjcmlwdGlvbiBzdHJpbmcgZm9yIGEgYEhhcm5lc3NMb2FkZXJgIHF1ZXJ5LiAqL1xuZnVuY3Rpb24gX2dldERlc2NyaXB0aW9uRm9ySGFybmVzc0xvYWRlclF1ZXJ5KHNlbGVjdG9yOiBzdHJpbmcpIHtcbiAgcmV0dXJuIGBIYXJuZXNzTG9hZGVyIGZvciBlbGVtZW50IG1hdGNoaW5nIHNlbGVjdG9yOiBcIiR7c2VsZWN0b3J9XCJgO1xufVxuIl19