lighthouse
Version:
> Stops you crashing into the rocks; lights the way
197 lines (169 loc) • 5.4 kB
JavaScript
/**
* @license
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
class Aggregate {
/**
* @throws {Error}
* @return {string} The name for this aggregation.
*/
static get name() {
throw new Error('Aggregate name must be overridden');
}
/**
* @throws {Error}
* @return {!AggregationCriteria} The criteria for this aggregation.
*/
static get criteria() {
throw new Error('Aggregate criteria must be overridden');
}
/**
* @private
* @param {!Array<!AuditResult>} results
* @param {!AggregationCriteria} expected
* @return {!Array<!AuditResult>}
*/
static _filterResultsByAuditNames(results, expected) {
const expectedNames = Object.keys(expected);
return results.filter(r => expectedNames.indexOf(r.name) !== -1);
}
/**
* @private
* @param {!AggregationCriteria} expected
* @return {number}
*/
static _getTotalWeight(expected) {
const expectedNames = Object.keys(expected);
let weight = expectedNames.reduce((last, e) => last + expected[e].weight, 0);
if (weight === 0) {
weight = 1;
}
return weight;
}
/**
* @private
* @param {!Array<!AuditResult>} results
* @return {!Object<!AuditResult>}
*/
static _remapResultsByName(results) {
const remapped = {};
results.forEach(r => {
if (remapped[r.name]) {
throw new Error(`Cannot remap: ${r.name} already exists`);
}
remapped[r.name] = r;
});
return remapped;
}
/**
* Converts each raw audit output to a weighted value for the aggregation.
* @private
* @param {!AuditResult} result The audit's output value.
* @param {!AggregationCriterion} expected The aggregation's expected value and weighting for this result.
* @return {number} The weighted result.
*/
static _convertToWeight(result, expected) {
let weight = 0;
if (typeof expected === 'undefined' ||
typeof expected.value === 'undefined' ||
typeof expected.weight === 'undefined') {
return weight;
}
if (typeof result === 'undefined' ||
typeof result.value === 'undefined') {
return weight;
}
if (typeof result.value !== typeof expected.value) {
return weight;
}
switch (typeof expected.value) {
case 'boolean':
weight = this._convertBooleanToWeight(result.value, expected.value, expected.weight);
break;
case 'number':
weight = this._convertNumberToWeight(result.value, expected.value, expected.weight);
break;
default:
weight = 0;
break;
}
return weight;
}
/**
* Converts a numeric result to a weight.
* @param {number} resultValue The result.
* @param {number} expectedValue The expected value.
* @param {number} weight The weight to assign.
* @return {number} The final weight.
*/
static _convertNumberToWeight(resultValue, expectedValue, weight) {
return (resultValue / expectedValue) * weight;
}
/**
* Converts a boolean result to a weight.
* @param {boolean} resultValue The result.
* @param {boolean} expectedValue The expected value.
* @param {number} weight The weight to assign.
* @return {number} The final weight.
*/
static _convertBooleanToWeight(resultValue, expectedValue, weight) {
return (resultValue === expectedValue) ? weight : 0;
}
/**
* Compares the set of audit results to the expected values.
* @param {!Array<!AuditResult>} results The audit results.
* @param {!AggregationCriteria} expected The aggregation's expected values and weighting.
* @return {!AggregationItem} The aggregation score.
*/
static compare(results, expected) {
const expectedNames = Object.keys(expected);
// Filter down and remap the results to something more comparable to
// the expected set of results.
const filteredAndRemappedResults =
Aggregate._remapResultsByName(
Aggregate._filterResultsByAuditNames(results, expected)
);
const maxScore = Aggregate._getTotalWeight(expected);
const subItems = [];
let overallScore = 0;
// Step through each item in the expected results, and
expectedNames.forEach(e => {
if (!filteredAndRemappedResults[e]) {
return;
}
overallScore += Aggregate._convertToWeight(
filteredAndRemappedResults[e],
expected[e]);
subItems.push(filteredAndRemappedResults[e]);
});
return {
overall: (overallScore / maxScore),
subItems: subItems
};
}
/**
* Aggregates all the results.
* @param {!Array<!AuditResult>} results
* @return {!Aggregation}
*/
static aggregate(results) {
return {
name: this.name,
score: Aggregate.compare(results, this.criteria)
};
}
}
module.exports = Aggregate;