/** * @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 */ /** Template string function that can be used to strip indentation from a given string literal. */ export function dedent(strings: TemplateStringsArray, ...values: any[]) { let joinedString = ''; for (let i = 0; i < values.length; i++) { joinedString += `${strings[i]}${values[i]}`; } joinedString += strings[strings.length - 1]; const lines = joinedString.split('\n'); while (isBlank(lines[0])) { lines.shift(); } while (isBlank(lines[lines.length - 1])) { lines.pop(); } let minWhitespacePrefix = lines.reduce( (min, line) => Math.min(min, numOfWhiteSpaceLeadingChars(line)), Number.MAX_SAFE_INTEGER); return lines.map((line) => line.substring(minWhitespacePrefix)).join('\n'); } /** * Tests to see if the line is blank. * * A blank line is such which contains only whitespace. * @param text string to test for blank-ness. */ function isBlank(text: string): boolean { return /^\s*$/.test(text); } /** * Returns number of whitespace leading characters. * * @param text */ function numOfWhiteSpaceLeadingChars(text: string): number { return text.match(/^\s*/)![0].length; } /** * Jasmine AsymmetricMatcher which can be used to assert `.debug` properties. * * ``` * expect(obj).toEqual({ * create: matchDebug('someValue') * }) * ``` * * In the above example it will assert that `obj.create.debug === 'someValue'`. * * @param expected Expected value. */ export function matchDebug(expected: T): any { const matcher = function() {}; let actual: any = matchDebug; matcher.asymmetricMatch = function(objectWithDebug: any) { return jasmine.matchersUtil.equals(actual = objectWithDebug.debug, expected); }; matcher.jasmineToString = function() { if (actual === matchDebug) { // `asymmetricMatch` never got called hence no error to display return ''; } return buildFailureMessage(actual, expected); }; return matcher; } export function buildFailureMessage(actual: any, expected: any): string { const diffs: string[] = []; listPropertyDifferences(diffs, '', actual, expected, 5); return '\n ' + diffs.join('\n '); } function listPropertyDifferences( diffs: string[], path: string, actual: any, expected: any, depth: number) { if (actual === expected) return; if (typeof actual !== typeof expected) { diffs.push(`${path}: Expected ${jasmine.pp(actual)} to be ${jasmine.pp(expected)}`); } else if (depth && Array.isArray(expected)) { if (!Array.isArray(actual)) { diffs.push(`${path}: Expected ${jasmine.pp(expected)} but was ${jasmine.pp(actual)}`); } else { const maxLength = Math.max(actual.length, expected.length); listPropertyDifferences(diffs, path + '.length', expected.length, actual.length, depth - 1); for (let i = 0; i < maxLength; i++) { const actualItem = actual[i]; const expectedItem = expected[i]; listPropertyDifferences(diffs, path + '[' + i + ']', actualItem, expectedItem, depth - 1); } } } else if ( depth && expected && typeof expected === 'object' && actual && typeof actual === 'object') { new Set(Object.keys(expected).concat(Object.keys(actual))).forEach((key) => { const actualItem = actual[key]; const expectedItem = expected[key]; listPropertyDifferences(diffs, path + '.' + key, actualItem, expectedItem, depth - 1); }); } else { diffs.push(`${path}: Expected ${jasmine.pp(actual)} to be ${jasmine.pp(expected)}`); } }