lighthouse 8.6.0-dev.20211023 → 8.6.0-dev.20211027

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/report/flow.js +10 -10
  2. package/dist/report/standalone.js +2 -2
  3. package/flow-report/assets/styles.css +57 -1
  4. package/flow-report/src/i18n/ui-strings.js +2 -0
  5. package/flow-report/src/summary/category.tsx +73 -4
  6. package/flow-report/src/summary/summary.tsx +1 -0
  7. package/flow-report/src/wrappers/markdown.tsx +27 -0
  8. package/flow-report/test/summary/category-test.tsx +111 -19
  9. package/flow-report/test/wrappers/markdown-test.tsx +29 -0
  10. package/jest.config.js +2 -2
  11. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
  12. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
  13. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
  14. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
  15. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
  16. package/lighthouse-core/audits/image-aspect-ratio.js +4 -3
  17. package/lighthouse-core/audits/image-size-responsive.js +3 -2
  18. package/lighthouse-core/audits/preload-lcp-image.js +7 -5
  19. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  20. package/lighthouse-core/audits/unsized-images.js +1 -2
  21. package/package.json +6 -5
  22. package/report/renderer/category-renderer.js +7 -0
  23. package/report/renderer/pwa-category-renderer.js +0 -10
  24. package/report/renderer/report-renderer.js +1 -1
  25. package/report/test/renderer/category-renderer-test.js +41 -0
  26. package/report/test/renderer/pwa-category-renderer-test.js +4 -4
  27. package/report/test/renderer/text-encoding-test.js +1 -1
  28. package/shared/localization/locales/ar-XB.json +8 -8
  29. package/shared/localization/locales/ar.json +8 -8
  30. package/shared/localization/locales/bg.json +8 -8
  31. package/shared/localization/locales/ca.json +8 -8
  32. package/shared/localization/locales/cs.json +8 -8
  33. package/shared/localization/locales/da.json +8 -8
  34. package/shared/localization/locales/de.json +8 -8
  35. package/shared/localization/locales/el.json +8 -8
  36. package/shared/localization/locales/en-GB.json +8 -8
  37. package/shared/localization/locales/en-US.json +27 -24
  38. package/shared/localization/locales/en-XA.json +8 -8
  39. package/shared/localization/locales/en-XL.json +27 -24
  40. package/shared/localization/locales/es-419.json +8 -8
  41. package/shared/localization/locales/es.json +8 -8
  42. package/shared/localization/locales/fi.json +8 -8
  43. package/shared/localization/locales/fil.json +8 -8
  44. package/shared/localization/locales/fr.json +8 -8
  45. package/shared/localization/locales/he.json +8 -8
  46. package/shared/localization/locales/hi.json +8 -8
  47. package/shared/localization/locales/hr.json +8 -8
  48. package/shared/localization/locales/hu.json +8 -8
  49. package/shared/localization/locales/id.json +8 -8
  50. package/shared/localization/locales/it.json +8 -8
  51. package/shared/localization/locales/ja.json +8 -8
  52. package/shared/localization/locales/ko.json +8 -8
  53. package/shared/localization/locales/lt.json +8 -8
  54. package/shared/localization/locales/lv.json +8 -8
  55. package/shared/localization/locales/nl.json +8 -8
  56. package/shared/localization/locales/no.json +8 -8
  57. package/shared/localization/locales/pl.json +8 -8
  58. package/shared/localization/locales/pt-PT.json +8 -8
  59. package/shared/localization/locales/pt.json +8 -8
  60. package/shared/localization/locales/ro.json +8 -8
  61. package/shared/localization/locales/ru.json +8 -8
  62. package/shared/localization/locales/sk.json +8 -8
  63. package/shared/localization/locales/sl.json +8 -8
  64. package/shared/localization/locales/sr-Latn.json +8 -8
  65. package/shared/localization/locales/sr.json +8 -8
  66. package/shared/localization/locales/sv.json +8 -8
  67. package/shared/localization/locales/ta.json +8 -8
  68. package/shared/localization/locales/te.json +8 -8
  69. package/shared/localization/locales/th.json +8 -8
  70. package/shared/localization/locales/tr.json +8 -8
  71. package/shared/localization/locales/uk.json +8 -8
  72. package/shared/localization/locales/vi.json +8 -8
  73. package/shared/localization/locales/zh-HK.json +8 -8
  74. package/shared/localization/locales/zh-TW.json +8 -8
  75. package/shared/localization/locales/zh.json +8 -8
  76. package/tsconfig-all.json +2 -2
  77. package/types/lhr/audit-details.d.ts +5 -5
@@ -63,7 +63,7 @@ class s{constructor(e){this._document=e,this._lighthouseChannel="unknown",this._
63
63
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
64
64
  * See the License for the specific language governing permissions and
65
65
  * limitations under the License.
66
- */class d{constructor(e,t){this.dom=e,this.detailsRenderer=t}get _clumpTitles(){return{warning:l.i18n.strings.warningAuditsGroupTitle,manual:l.i18n.strings.manualAuditsGroupTitle,passed:l.i18n.strings.passedAuditsGroupTitle,notApplicable:l.i18n.strings.notApplicableAuditsGroupTitle}}renderAudit(e){const t=this.dom.createComponent("audit");return this.populateAuditValues(e,t)}populateAuditValues(e,t){const n=l.i18n.strings,r=this.dom.find(".lh-audit",t);r.id=e.result.id;const o=e.result.scoreDisplayMode;e.result.displayValue&&(this.dom.find(".lh-audit__display-text",r).textContent=e.result.displayValue);const i=this.dom.find(".lh-audit__title",r);i.appendChild(this.dom.convertMarkdownCodeSnippets(e.result.title));const a=this.dom.find(".lh-audit__description",r);a.appendChild(this.dom.convertMarkdownLinkSnippets(e.result.description));for(const t of e.relevantMetrics||[]){const e=this.dom.createChildOf(a,"span","lh-audit__adorn");e.title=`Relevant to ${t.result.title}`,e.textContent=t.acronym||t.id}e.stackPacks&&e.stackPacks.forEach((e=>{const t=this.dom.createElement("div");t.classList.add("lh-audit__stackpack");const n=this.dom.createElement("img");n.classList.add("lh-audit__stackpack__img"),n.src=e.iconDataURL,n.alt=e.title,t.appendChild(n),t.appendChild(this.dom.convertMarkdownLinkSnippets(e.description)),this.dom.find(".lh-audit__stackpacks",r).appendChild(t)}));const s=this.dom.find("details",r);if(e.result.details){const t=this.detailsRenderer.render(e.result.details);t&&(t.classList.add("lh-details"),s.appendChild(t))}if(this.dom.find(".lh-chevron-container",r).appendChild(this._createChevron()),this._setRatingClass(r,e.result.score,o),"error"===e.result.scoreDisplayMode){r.classList.add("lh-audit--error");const t=this.dom.find(".lh-audit__display-text",r);t.textContent=n.errorLabel,t.classList.add("lh-tooltip-boundary");this.dom.createChildOf(t,"div","lh-tooltip lh-tooltip--error").textContent=e.result.errorMessage||n.errorMissingAuditInfo}else if(e.result.explanation){this.dom.createChildOf(i,"div","lh-audit-explanation").textContent=e.result.explanation}const c=e.result.warnings;if(!c||0===c.length)return r;const d=this.dom.find("summary",s),h=this.dom.createChildOf(d,"div","lh-warnings");if(this.dom.createChildOf(h,"span").textContent=n.warningHeader,1===c.length)h.appendChild(this.dom.document().createTextNode(c.join("")));else{const e=this.dom.createChildOf(h,"ul");for(const t of c){this.dom.createChildOf(e,"li").textContent=t}}return r}injectFinalScreenshot(e,t,n){const r=t["final-screenshot"];if(!r||"error"===r.scoreDisplayMode)return null;if(!r.details||"screenshot"!==r.details.type)return null;const o=this.dom.createElement("img","lh-final-ss-image"),i=r.details.data;o.src=i,o.alt=r.title;const a=this.dom.find(".lh-category .lh-category-header",e),l=this.dom.createElement("div","lh-category-headercol"),s=this.dom.createElement("div","lh-category-headercol lh-category-headercol--separator"),c=this.dom.createElement("div","lh-category-headercol");l.append(...a.childNodes),l.append(n),c.append(o),a.append(l,s,c),a.classList.add("lh-category-header__finalscreenshot")}_createChevron(){const e=this.dom.createComponent("chevron");return this.dom.find("svg.lh-chevron",e)}_setRatingClass(e,t,n){const r=l.calculateRating(t,n);return e.classList.add(`lh-audit--${n.toLowerCase()}`),"informative"!==n&&e.classList.add(`lh-audit--${r}`),e}renderCategoryHeader(e,t,n){const r=this.dom.createComponent("categoryHeader"),o=this.dom.find(".lh-score__gauge",r),i=this.renderCategoryScore(e,t,n);if(o.appendChild(i),e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);this.dom.find(".lh-category-header__description",r).appendChild(t)}return r}renderAuditGroup(e){const t=this.dom.createElement("div","lh-audit-group"),n=this.dom.createElement("div","lh-audit-group__header");if(this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);t.classList.add("lh-audit-group__description"),n.appendChild(t)}return t.appendChild(n),t}_renderGroupedAudits(e,t){const n=new Map,r="NotAGroup";n.set(r,[]);for(const t of e){const e=t.group||r,o=n.get(e)||[];o.push(t),n.set(e,o)}const o=[];for(const[e,i]of n){if(e===r){for(const e of i)o.push(this.renderAudit(e));continue}const n=t[e],a=this.renderAuditGroup(n);for(const e of i)a.appendChild(this.renderAudit(e));a.classList.add(`lh-audit-group--${e}`),o.push(a)}return o}renderUnexpandableClump(e,t){const n=this.dom.createElement("div");return this._renderGroupedAudits(e,t).forEach((e=>n.appendChild(e))),n}renderClump(e,{auditRefs:t,description:n}){const r=this.dom.createComponent("clump"),o=this.dom.find(".lh-clump",r);"warning"===e&&o.setAttribute("open","");this.dom.find("div.lh-audit-group__summary",o).appendChild(this._createChevron());const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];if(this.dom.find(".lh-audit-group__title",i).textContent=a,n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description"),i.appendChild(e)}this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const l=t.map(this.renderAudit.bind(this));return o.append(...l),o.classList.add(`lh-clump--${e.toLowerCase()}`),o}renderCategoryScore(e,t,n){return n&&l.shouldDisplayAsFraction(n.gatherMode)?this.renderCategoryFraction(e):this.renderScoreGauge(e,t)}renderScoreGauge(e,t){const n=this.dom.createComponent("gauge"),r=this.dom.find("a.lh-gauge__wrapper",n);l.isPluginCategory(e.id)&&r.classList.add("lh-gauge__wrapper--plugin");const o=Number(e.score),i=this.dom.find(".lh-gauge",n),a=this.dom.find("circle.lh-gauge-arc",i);a&&this._setGaugeArc(a,o);const s=Math.round(100*o),c=this.dom.find("div.lh-gauge__percentage",n);return c.textContent=s.toString(),null===e.score&&(c.textContent="?",c.title=l.i18n.strings.errorLabel),0===e.auditRefs.length||this.hasApplicableAudits(e)?r.classList.add(`lh-gauge__wrapper--${l.calculateRating(e.score)}`):(r.classList.add("lh-gauge__wrapper--not-applicable"),c.textContent="-",c.title=l.i18n.strings.notApplicableAuditsGroupTitle),this.dom.find(".lh-gauge__label",n).textContent=e.title,n}renderCategoryFraction(e){const t=this.dom.createComponent("fraction"),n=this.dom.find("a.lh-fraction__wrapper",t),{numPassed:r,numPassableAudits:o,totalWeight:i}=l.calculateCategoryFraction(e),a=r/o,s=this.dom.find(".lh-fraction__content",t),c=this.dom.createElement("span");c.textContent=`${r}/${o}`,s.appendChild(c);let d=l.calculateRating(a);return 0===i&&(d="null"),n.classList.add(`lh-fraction__wrapper--${d}`),this.dom.find(".lh-fraction__label",t).textContent=e.title,t}hasApplicableAudits(e){return e.auditRefs.some((e=>"notApplicable"!==e.result.scoreDisplayMode))}_setGaugeArc(e,t){const n=2*Math.PI*Number(e.getAttribute("r")),r=Number(e.getAttribute("stroke-width")),o=.25*r/n;e.style.transform=`rotate(${360*o-90}deg)`;let i=t*n-r/2;0===t&&(e.style.opacity="0"),1===t&&(i=n),e.style.strokeDasharray=`${Math.max(i,0)} ${n}`}_auditHasWarning(e){return Boolean(e.result.warnings&&e.result.warnings.length)}_getClumpIdForAuditRef(e){const t=e.result.scoreDisplayMode;return"manual"===t||"notApplicable"===t?t:l.showAsPassed(e.result)?this._auditHasWarning(e)?"warning":"passed":"failed"}render(e,t={},n){const r=this.dom.createElement("div","lh-category");r.id=e.id,r.appendChild(this.renderCategoryHeader(e,t,n));const o=new Map;o.set("failed",[]),o.set("warning",[]),o.set("manual",[]),o.set("passed",[]),o.set("notApplicable",[]);for(const t of e.auditRefs){const e=this._getClumpIdForAuditRef(t),n=o.get(e);n.push(t),o.set(e,n)}for(const[n,i]of o){if(0===i.length)continue;if("failed"===n){const e=this.renderUnexpandableClump(i,t);e.classList.add("lh-clump--failed"),r.appendChild(e);continue}const o="manual"===n?e.manualDescription:void 0,a=this.renderClump(n,{auditRefs:i,description:o});r.appendChild(a)}return r}}
66
+ */class d{constructor(e,t){this.dom=e,this.detailsRenderer=t}get _clumpTitles(){return{warning:l.i18n.strings.warningAuditsGroupTitle,manual:l.i18n.strings.manualAuditsGroupTitle,passed:l.i18n.strings.passedAuditsGroupTitle,notApplicable:l.i18n.strings.notApplicableAuditsGroupTitle}}renderAudit(e){const t=this.dom.createComponent("audit");return this.populateAuditValues(e,t)}populateAuditValues(e,t){const n=l.i18n.strings,r=this.dom.find(".lh-audit",t);r.id=e.result.id;const o=e.result.scoreDisplayMode;e.result.displayValue&&(this.dom.find(".lh-audit__display-text",r).textContent=e.result.displayValue);const i=this.dom.find(".lh-audit__title",r);i.appendChild(this.dom.convertMarkdownCodeSnippets(e.result.title));const a=this.dom.find(".lh-audit__description",r);a.appendChild(this.dom.convertMarkdownLinkSnippets(e.result.description));for(const t of e.relevantMetrics||[]){const e=this.dom.createChildOf(a,"span","lh-audit__adorn");e.title=`Relevant to ${t.result.title}`,e.textContent=t.acronym||t.id}e.stackPacks&&e.stackPacks.forEach((e=>{const t=this.dom.createElement("div");t.classList.add("lh-audit__stackpack");const n=this.dom.createElement("img");n.classList.add("lh-audit__stackpack__img"),n.src=e.iconDataURL,n.alt=e.title,t.appendChild(n),t.appendChild(this.dom.convertMarkdownLinkSnippets(e.description)),this.dom.find(".lh-audit__stackpacks",r).appendChild(t)}));const s=this.dom.find("details",r);if(e.result.details){const t=this.detailsRenderer.render(e.result.details);t&&(t.classList.add("lh-details"),s.appendChild(t))}if(this.dom.find(".lh-chevron-container",r).appendChild(this._createChevron()),this._setRatingClass(r,e.result.score,o),"error"===e.result.scoreDisplayMode){r.classList.add("lh-audit--error");const t=this.dom.find(".lh-audit__display-text",r);t.textContent=n.errorLabel,t.classList.add("lh-tooltip-boundary");this.dom.createChildOf(t,"div","lh-tooltip lh-tooltip--error").textContent=e.result.errorMessage||n.errorMissingAuditInfo}else if(e.result.explanation){this.dom.createChildOf(i,"div","lh-audit-explanation").textContent=e.result.explanation}const c=e.result.warnings;if(!c||0===c.length)return r;const d=this.dom.find("summary",s),h=this.dom.createChildOf(d,"div","lh-warnings");if(this.dom.createChildOf(h,"span").textContent=n.warningHeader,1===c.length)h.appendChild(this.dom.document().createTextNode(c.join("")));else{const e=this.dom.createChildOf(h,"ul");for(const t of c){this.dom.createChildOf(e,"li").textContent=t}}return r}injectFinalScreenshot(e,t,n){const r=t["final-screenshot"];if(!r||"error"===r.scoreDisplayMode)return null;if(!r.details||"screenshot"!==r.details.type)return null;const o=this.dom.createElement("img","lh-final-ss-image"),i=r.details.data;o.src=i,o.alt=r.title;const a=this.dom.find(".lh-category .lh-category-header",e),l=this.dom.createElement("div","lh-category-headercol"),s=this.dom.createElement("div","lh-category-headercol lh-category-headercol--separator"),c=this.dom.createElement("div","lh-category-headercol");l.append(...a.childNodes),l.append(n),c.append(o),a.append(l,s,c),a.classList.add("lh-category-header__finalscreenshot")}_createChevron(){const e=this.dom.createComponent("chevron");return this.dom.find("svg.lh-chevron",e)}_setRatingClass(e,t,n){const r=l.calculateRating(t,n);return e.classList.add(`lh-audit--${n.toLowerCase()}`),"informative"!==n&&e.classList.add(`lh-audit--${r}`),e}renderCategoryHeader(e,t,n){const r=this.dom.createComponent("categoryHeader"),o=this.dom.find(".lh-score__gauge",r),i=this.renderCategoryScore(e,t,n);if(o.appendChild(i),e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);this.dom.find(".lh-category-header__description",r).appendChild(t)}return r}renderAuditGroup(e){const t=this.dom.createElement("div","lh-audit-group"),n=this.dom.createElement("div","lh-audit-group__header");if(this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);t.classList.add("lh-audit-group__description"),n.appendChild(t)}return t.appendChild(n),t}_renderGroupedAudits(e,t){const n=new Map,r="NotAGroup";n.set(r,[]);for(const t of e){const e=t.group||r,o=n.get(e)||[];o.push(t),n.set(e,o)}const o=[];for(const[e,i]of n){if(e===r){for(const e of i)o.push(this.renderAudit(e));continue}const n=t[e],a=this.renderAuditGroup(n);for(const e of i)a.appendChild(this.renderAudit(e));a.classList.add(`lh-audit-group--${e}`),o.push(a)}return o}renderUnexpandableClump(e,t){const n=this.dom.createElement("div");return this._renderGroupedAudits(e,t).forEach((e=>n.appendChild(e))),n}renderClump(e,{auditRefs:t,description:n}){const r=this.dom.createComponent("clump"),o=this.dom.find(".lh-clump",r);"warning"===e&&o.setAttribute("open","");this.dom.find("div.lh-audit-group__summary",o).appendChild(this._createChevron());const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];if(this.dom.find(".lh-audit-group__title",i).textContent=a,n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description"),i.appendChild(e)}this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const l=t.map(this.renderAudit.bind(this));return o.append(...l),o.classList.add(`lh-clump--${e.toLowerCase()}`),o}renderCategoryScore(e,t,n){return n&&l.shouldDisplayAsFraction(n.gatherMode)?this.renderCategoryFraction(e):this.renderScoreGauge(e,t)}renderScoreGauge(e,t){const n=this.dom.createComponent("gauge"),r=this.dom.find("a.lh-gauge__wrapper",n);l.isPluginCategory(e.id)&&r.classList.add("lh-gauge__wrapper--plugin");const o=Number(e.score),i=this.dom.find(".lh-gauge",n),a=this.dom.find("circle.lh-gauge-arc",i);a&&this._setGaugeArc(a,o);const s=Math.round(100*o),c=this.dom.find("div.lh-gauge__percentage",n);return c.textContent=s.toString(),null===e.score&&(c.textContent="?",c.title=l.i18n.strings.errorLabel),0===e.auditRefs.length||this.hasApplicableAudits(e)?r.classList.add(`lh-gauge__wrapper--${l.calculateRating(e.score)}`):(r.classList.add("lh-gauge__wrapper--not-applicable"),c.textContent="-",c.title=l.i18n.strings.notApplicableAuditsGroupTitle),this.dom.find(".lh-gauge__label",n).textContent=e.title,n}renderCategoryFraction(e){const t=this.dom.createComponent("fraction"),n=this.dom.find("a.lh-fraction__wrapper",t),{numPassed:r,numPassableAudits:o,totalWeight:i}=l.calculateCategoryFraction(e),a=r/o,s=this.dom.find(".lh-fraction__content",t),c=this.dom.createElement("span");c.textContent=`${r}/${o}`,s.appendChild(c);let d=l.calculateRating(a);return 0===i&&(d="null"),n.classList.add(`lh-fraction__wrapper--${d}`),this.dom.find(".lh-fraction__label",t).textContent=e.title,t}hasApplicableAudits(e){return e.auditRefs.some((e=>"notApplicable"!==e.result.scoreDisplayMode))}_setGaugeArc(e,t){const n=2*Math.PI*Number(e.getAttribute("r")),r=Number(e.getAttribute("stroke-width")),o=.25*r/n;e.style.transform=`rotate(${360*o-90}deg)`;let i=t*n-r/2;0===t&&(e.style.opacity="0"),1===t&&(i=n),e.style.strokeDasharray=`${Math.max(i,0)} ${n}`}_auditHasWarning(e){return Boolean(e.result.warnings&&e.result.warnings.length)}_getClumpIdForAuditRef(e){const t=e.result.scoreDisplayMode;return"manual"===t||"notApplicable"===t?t:l.showAsPassed(e.result)?this._auditHasWarning(e)?"warning":"passed":"failed"}render(e,t={},n){const r=this.dom.createElement("div","lh-category");r.id=e.id,r.appendChild(this.renderCategoryHeader(e,t,n));const o=new Map;o.set("failed",[]),o.set("warning",[]),o.set("manual",[]),o.set("passed",[]),o.set("notApplicable",[]);for(const t of e.auditRefs){const e=this._getClumpIdForAuditRef(t),n=o.get(e);n.push(t),o.set(e,n)}for(const e of o.values())e.sort(((e,t)=>t.weight-e.weight));for(const[n,i]of o){if(0===i.length)continue;if("failed"===n){const e=this.renderUnexpandableClump(i,t);e.classList.add("lh-clump--failed"),r.appendChild(e);continue}const o="manual"===n?e.manualDescription:void 0,a=this.renderClump(n,{auditRefs:i,description:o});r.appendChild(a)}return r}}
67
67
  /**
68
68
  * @license
69
69
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -142,7 +142,7 @@ class s{constructor(e){this._document=e,this._lighthouseChannel="unknown",this._
142
142
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143
143
  * See the License for the specific language governing permissions and
144
144
  * limitations under the License.
145
- */class N extends d{render(e,t={}){const n=this.dom.createElement("div","lh-category");n.id=e.id,n.appendChild(this.renderCategoryHeader(e,t));const r=e.auditRefs,o=r.filter((e=>"manual"!==e.result.scoreDisplayMode)),i=this._renderAudits(o,t);n.appendChild(i);const a=r.filter((e=>"manual"===e.result.scoreDisplayMode)),l=this.renderClump("manual",{auditRefs:a,description:e.manualDescription});return n.appendChild(l),n}renderScoreGauge(e,t){return this.renderCategoryScore(e,t)}renderCategoryScore(e,t){if(null===e.score)return super.renderScoreGauge(e,t);const n=this.dom.createComponent("gaugePwa"),r=this.dom.find("a.lh-gauge--pwa__wrapper",n),o=n.querySelector("svg");if(!o)throw new Error("no SVG element found in PWA score gauge template");N._makeSvgReferencesUnique(o);const i=this._getGroupIds(e.auditRefs),a=this._getPassingGroupIds(e.auditRefs);if(a.size===i.size)r.classList.add("lh-badged--all");else for(const e of a)r.classList.add(`lh-badged--${e}`);return this.dom.find(".lh-gauge__label",n).textContent=e.title,r.title=this._getGaugeTooltip(e.auditRefs,t),n}_getGroupIds(e){const t=e.map((e=>e.group)).filter((e=>!!e));return new Set(t)}_getPassingGroupIds(e){const t=this._getGroupIds(e);for(const n of e)!l.showAsPassed(n.result)&&n.group&&t.delete(n.group);return t}_getGaugeTooltip(e,t){const n=this._getGroupIds(e),r=[];for(const o of n){const n=e.filter((e=>e.group===o)),i=n.length,a=n.filter((e=>l.showAsPassed(e.result))).length,s=t[o].title;r.push(`${s}: ${a}/${i}`)}return r.join(", ")}_renderAudits(e,t){const n=this.renderUnexpandableClump(e,t),r=this._getPassingGroupIds(e);for(const e of r){this.dom.find(`.lh-audit-group--${e}`,n).classList.add("lh-badged")}return n}static _makeSvgReferencesUnique(e){const t=e.querySelector("defs");if(!t)return;const n=l.getUniqueSuffix(),r=t.querySelectorAll("[id]");for(const t of r){const r=t.id,o=`${r}-${n}`;t.id=o;const i=e.querySelectorAll(`use[href="#${r}"]`);for(const e of i)e.setAttribute("href",`#${o}`);const a=e.querySelectorAll(`[fill="url(#${r})"]`);for(const e of a)e.setAttribute("fill",`url(#${o})`)}}}
145
+ */class N extends d{render(e,t={}){const n=this.dom.createElement("div","lh-category");n.id=e.id,n.appendChild(this.renderCategoryHeader(e,t));const r=e.auditRefs,o=r.filter((e=>"manual"!==e.result.scoreDisplayMode)),i=this._renderAudits(o,t);n.appendChild(i);const a=r.filter((e=>"manual"===e.result.scoreDisplayMode)),l=this.renderClump("manual",{auditRefs:a,description:e.manualDescription});return n.appendChild(l),n}renderCategoryScore(e,t){if(null===e.score)return super.renderScoreGauge(e,t);const n=this.dom.createComponent("gaugePwa"),r=this.dom.find("a.lh-gauge--pwa__wrapper",n),o=n.querySelector("svg");if(!o)throw new Error("no SVG element found in PWA score gauge template");N._makeSvgReferencesUnique(o);const i=this._getGroupIds(e.auditRefs),a=this._getPassingGroupIds(e.auditRefs);if(a.size===i.size)r.classList.add("lh-badged--all");else for(const e of a)r.classList.add(`lh-badged--${e}`);return this.dom.find(".lh-gauge__label",n).textContent=e.title,r.title=this._getGaugeTooltip(e.auditRefs,t),n}_getGroupIds(e){const t=e.map((e=>e.group)).filter((e=>!!e));return new Set(t)}_getPassingGroupIds(e){const t=this._getGroupIds(e);for(const n of e)!l.showAsPassed(n.result)&&n.group&&t.delete(n.group);return t}_getGaugeTooltip(e,t){const n=this._getGroupIds(e),r=[];for(const o of n){const n=e.filter((e=>e.group===o)),i=n.length,a=n.filter((e=>l.showAsPassed(e.result))).length,s=t[o].title;r.push(`${s}: ${a}/${i}`)}return r.join(", ")}_renderAudits(e,t){const n=this.renderUnexpandableClump(e,t),r=this._getPassingGroupIds(e);for(const e of r){this.dom.find(`.lh-audit-group--${e}`,n).classList.add("lh-badged")}return n}static _makeSvgReferencesUnique(e){const t=e.querySelector("defs");if(!t)return;const n=l.getUniqueSuffix(),r=t.querySelectorAll("[id]");for(const t of r){const r=t.id,o=`${r}-${n}`;t.id=o;const i=e.querySelectorAll(`use[href="#${r}"]`);for(const e of i)e.setAttribute("href",`#${o}`);const a=e.querySelectorAll(`[fill="url(#${r})"]`);for(const e of a)e.setAttribute("fill",`url(#${o})`)}}}
146
146
  /**
147
147
  * @license
148
148
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -31,6 +31,7 @@
31
31
  --summary-subtitle-font-size: 12px;
32
32
  --summary-title-font-size: 20px;
33
33
  --summary-tooltip-box-shadow-color: rgba(0, 0, 0, 0.25);
34
+ --summary-tooltip-line-height: 16px;
34
35
  --topbar-button-font-size: 14px;
35
36
  --topbar-button-size: 45px;
36
37
  --topbar-title-font-size: 14px;
@@ -394,22 +395,33 @@
394
395
  border-radius: 3px;
395
396
  padding: var(--base-spacing);
396
397
  right: 0;
398
+ line-height: var(--summary-tooltip-line-height);
397
399
  box-shadow: 0px 4px 4px var(--summary-tooltip-box-shadow-color);
398
400
  z-index: 1;
399
401
  }
400
402
 
401
403
  .SummaryTooltip__title {
402
404
  font-weight: var(--bold-weight);
403
- margin-bottom: var(--half-base-spacing);
405
+ }
406
+
407
+ .SummaryTooltip__url {
408
+ overflow-x: hidden;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ margin-top: calc(0.25 * var(--base-spacing));
412
+ margin-bottom: calc(0.75 * var(--base-spacing));
413
+ color: var(--color-gray-700);
404
414
  }
405
415
 
406
416
  .SummaryTooltip__category {
407
417
  font-weight: var(--bold-weight);
408
418
  display: flex;
419
+ align-items: center;
409
420
  margin-top: var(--half-base-spacing);
410
421
  }
411
422
 
412
423
  .SummaryTooltip__category-title {
424
+ line-height: 24px;
413
425
  flex-grow: 1;
414
426
  }
415
427
 
@@ -424,6 +436,50 @@
424
436
  color: var(--color-fail);
425
437
  }
426
438
 
439
+ .SummaryTooltip__fraction {
440
+ color: var(--color-gray-700);
441
+ }
442
+
443
+ .SummaryTooltip__informative {
444
+ margin-top: calc(0.25 * var(--base-spacing));
445
+ color: var(--color-gray-700);
446
+ }
447
+
448
+ .SummaryTooltipAudits__title {
449
+ line-height: 24px;
450
+ margin-top: calc(0.75 * var(--base-spacing));
451
+ font-weight: var(--bold-weight);
452
+ }
453
+
454
+ .SummaryTooltipAudit {
455
+ display: flex;
456
+ margin: calc(0.25 * var(--base-spacing)) 0px;
457
+ color: var(--color-gray-700);
458
+ --score-icon-size: 10px;
459
+ }
460
+ .SummaryTooltipAudit code {
461
+ color: var(--snippet-color);
462
+ }
463
+ .SummaryTooltipAudit::before {
464
+ content: '';
465
+ height: var(--score-icon-size);
466
+ width: var(--score-icon-size);
467
+ margin-top: calc((var(--summary-tooltip-line-height) - var(--score-icon-size)) / 2);
468
+ margin-right: var(--half-base-spacing);
469
+ }
470
+ .SummaryTooltipAudit--pass::before {
471
+ background-color: var(--color-pass);
472
+ border-radius: 50%;
473
+ }
474
+ .SummaryTooltipAudit--average::before {
475
+ background-color: var(--color-average);
476
+ }
477
+ .SummaryTooltipAudit--fail::before {
478
+ border-left: calc(var(--score-icon-size) / 2) solid transparent;
479
+ border-right: calc(var(--score-icon-size) / 2) solid transparent;
480
+ border-bottom: var(--score-icon-size) solid var(--color-fail);
481
+ }
482
+
427
483
  .SummaryNavigationHeader {
428
484
  font-size: var(--summary-navigation-header-font-size);
429
485
  line-height: 16px;
@@ -131,4 +131,6 @@ export const UIStrings = {
131
131
  =1 {{numInformative} informative audit}
132
132
  other {{numInformative} informative audits}
133
133
  }`,
134
+ /** Label for a list of Lighthouse audits that are the most impactful. */
135
+ highestImpact: 'Highest impact',
134
136
  };
@@ -10,9 +10,14 @@ import {Util} from '../../../report/renderer/util';
10
10
  import {Separator} from '../common';
11
11
  import {CategoryScore} from '../wrappers/category-score';
12
12
  import {useI18n, useStringFormatter, useLocalizedStrings} from '../i18n/i18n';
13
+ import {Markdown} from '../wrappers/markdown';
13
14
 
14
15
  import type {UIStringsType} from '../i18n/ui-strings';
15
16
 
17
+ const MAX_TOOLTIP_AUDITS = 2;
18
+
19
+ type ScoredAuditRef = LH.ReportResult.AuditRef & {result: {score: number}};
20
+
16
21
  function getGatherModeLabel(gatherMode: LH.Result.GatherMode, strings: UIStringsType) {
17
22
  switch (gatherMode) {
18
23
  case 'navigation': return strings.navigationReport;
@@ -30,10 +35,71 @@ function getCategoryRating(rating: string, strings: UIStringsType) {
30
35
  }
31
36
  }
32
37
 
38
+ function getScoreToBeGained(audit: ScoredAuditRef): number {
39
+ return audit.weight * (1 - audit.result.score);
40
+ }
41
+
42
+ function getOverallSavings(audit: LH.ReportResult.AuditRef): number {
43
+ return (
44
+ audit.result.details &&
45
+ audit.result.details.type === 'opportunity' &&
46
+ audit.result.details.overallSavingsMs
47
+ ) || 0;
48
+ }
49
+
50
+ const SummaryTooltipAudit: FunctionComponent<{audit: LH.ReportResult.AuditRef}> = ({audit}) => {
51
+ const rating = Util.calculateRating(audit.result.score, audit.result.scoreDisplayMode);
52
+ return (
53
+ <div className={`SummaryTooltipAudit SummaryTooltipAudit--${rating}`}>
54
+ <Markdown text={audit.result.title}/>
55
+ </div>
56
+ );
57
+ };
58
+
59
+ const SummaryTooltipAudits: FunctionComponent<{category: LH.ReportResult.Category}> =
60
+ ({category}) => {
61
+ const strings = useLocalizedStrings();
62
+
63
+ function isRelevantAudit(audit: LH.ReportResult.AuditRef): audit is ScoredAuditRef {
64
+ return audit.result.score !== null &&
65
+ // Metrics should not be displayed in this group.
66
+ audit.group !== 'metrics' &&
67
+ // Audits in performance without a group are hidden.
68
+ (audit.group !== undefined || category.id !== 'performance') &&
69
+ // We don't want unweighted audits except for opportunities with potential savings.
70
+ (audit.weight > 0 || getOverallSavings(audit) > 0) &&
71
+ // Passing audits should never be high impact.
72
+ !Util.showAsPassed(audit.result);
73
+ }
74
+
75
+ const audits = category.auditRefs
76
+ .filter(isRelevantAudit)
77
+ .sort((a, b) => {
78
+ // Remaining score should always be 0 for perf opportunities because weight is 0.
79
+ // In that case, we want to sort by `overallSavingsMs`.
80
+ const remainingScoreA = getScoreToBeGained(a);
81
+ const remainingScoreB = getScoreToBeGained(b);
82
+ if (remainingScoreA !== remainingScoreB) return remainingScoreB - remainingScoreA;
83
+ return getOverallSavings(b) - getOverallSavings(a);
84
+ })
85
+ .splice(0, MAX_TOOLTIP_AUDITS);
86
+ if (!audits.length) return null;
87
+
88
+ return (
89
+ <div className="SummaryTooltipAudits">
90
+ <div className="SummaryTooltipAudits__title">{strings.highestImpact}</div>
91
+ {
92
+ audits.map(audit => <SummaryTooltipAudit key={audit.id} audit={audit}/>)
93
+ }
94
+ </div>
95
+ );
96
+ };
97
+
33
98
  export const SummaryTooltip: FunctionComponent<{
34
99
  category: LH.ReportResult.Category,
35
- gatherMode: LH.Result.GatherMode
36
- }> = ({category, gatherMode}) => {
100
+ gatherMode: LH.Result.GatherMode,
101
+ url: string,
102
+ }> = ({category, gatherMode, url}) => {
37
103
  const strings = useLocalizedStrings();
38
104
  const str_ = useStringFormatter();
39
105
  const {
@@ -53,6 +119,7 @@ export const SummaryTooltip: FunctionComponent<{
53
119
  return (
54
120
  <div className="SummaryTooltip">
55
121
  <div className="SummaryTooltip__title">{getGatherModeLabel(gatherMode, strings)}</div>
122
+ <div className="SummaryTooltip__url">{url}</div>
56
123
  <Separator/>
57
124
  <div className="SummaryTooltip__category">
58
125
  <div className="SummaryTooltip__category-title">
@@ -81,6 +148,7 @@ export const SummaryTooltip: FunctionComponent<{
81
148
  {str_(strings.informativeAuditCount, {numInformative})}
82
149
  </div>
83
150
  }
151
+ <SummaryTooltipAudits category={category}/>
84
152
  </div>
85
153
  );
86
154
  };
@@ -89,7 +157,8 @@ export const SummaryCategory: FunctionComponent<{
89
157
  category: LH.ReportResult.Category|undefined,
90
158
  href: string,
91
159
  gatherMode: LH.Result.GatherMode,
92
- }> = ({category, href, gatherMode}) => {
160
+ finalUrl: string,
161
+ }> = ({category, href, gatherMode, finalUrl}) => {
93
162
  return (
94
163
  <div className="SummaryCategory">
95
164
  {
@@ -100,7 +169,7 @@ export const SummaryCategory: FunctionComponent<{
100
169
  href={href}
101
170
  gatherMode={gatherMode}
102
171
  />
103
- <SummaryTooltip category={category} gatherMode={gatherMode}/>
172
+ <SummaryTooltip category={category} gatherMode={gatherMode} url={finalUrl}/>
104
173
  </div> :
105
174
  <div className="SummaryCategory__null" data-testid="SummaryCategory__null"/>
106
175
  }
@@ -76,6 +76,7 @@ export const SummaryFlowStep: FunctionComponent<{
76
76
  category={reportResult.categories[c]}
77
77
  href={`#index=${hashIndex}&anchor=${c}`}
78
78
  gatherMode={lhr.gatherMode}
79
+ finalUrl={lhr.finalUrl}
79
80
  />
80
81
  ))
81
82
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+
7
+ import {FunctionComponent} from 'preact';
8
+ import {useLayoutEffect, useRef} from 'preact/hooks';
9
+
10
+ import {useReportRenderer} from '../wrappers/report-renderer';
11
+
12
+ export const Markdown: FunctionComponent<{text: string}> = ({text}) => {
13
+ const {dom} = useReportRenderer();
14
+ const ref = useRef<HTMLSpanElement>(null);
15
+
16
+ useLayoutEffect(() => {
17
+ if (ref.current) {
18
+ const md = dom.convertMarkdownCodeSnippets(text);
19
+ ref.current.appendChild(md);
20
+ }
21
+ return () => {
22
+ if (ref.current) ref.current.innerHTML = '';
23
+ };
24
+ }, [text]);
25
+
26
+ return <span ref={ref}/>;
27
+ };
@@ -11,6 +11,7 @@ import {SummaryTooltip} from '../../src/summary/category';
11
11
  import {flowResult} from '../sample-flow';
12
12
  import {I18nProvider} from '../../src/i18n/i18n';
13
13
  import {FlowResultContext} from '../../src/util';
14
+ import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
14
15
 
15
16
  let wrapper: FunctionComponent;
16
17
 
@@ -18,9 +19,11 @@ beforeEach(() => {
18
19
  // Include sample flowResult for locale in I18nProvider.
19
20
  wrapper = ({children}) => (
20
21
  <FlowResultContext.Provider value={flowResult}>
21
- <I18nProvider>
22
- {children}
23
- </I18nProvider>
22
+ <ReportRendererProvider>
23
+ <I18nProvider>
24
+ {children}
25
+ </I18nProvider>
26
+ </ReportRendererProvider>
24
27
  </FlowResultContext.Provider>
25
28
  );
26
29
  });
@@ -31,14 +34,16 @@ describe('SummaryTooltip', () => {
31
34
  id: 'performance',
32
35
  score: 1,
33
36
  auditRefs: [
34
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
35
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
36
- {result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
37
+ /* eslint-disable max-len */
38
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
39
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
40
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
41
+ /* eslint-enable max-len */
37
42
  ],
38
43
  };
39
44
 
40
45
  const root = render(
41
- <SummaryTooltip category={category} gatherMode="snapshot"/>,
46
+ <SummaryTooltip category={category} gatherMode="snapshot" url="https://example.com"/>,
42
47
  {wrapper}
43
48
  );
44
49
 
@@ -46,6 +51,7 @@ describe('SummaryTooltip', () => {
46
51
  expect(() => root.getByText(/^[0-9]+$/)).toThrow();
47
52
  expect(root.getByText('2 audits passed')).toBeTruthy();
48
53
  expect(root.getByText('3 passable audits')).toBeTruthy();
54
+ expect(root.getByText('https://example.com'));
49
55
  });
50
56
 
51
57
  it('renders tooltip without rating', async () => {
@@ -53,14 +59,16 @@ describe('SummaryTooltip', () => {
53
59
  id: 'performance',
54
60
  score: 1,
55
61
  auditRefs: [
56
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
57
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
58
- {result: {score: 0, scoreDisplayMode: 'binary'}, weight: 0, group: 'diagnostics'},
62
+ /* eslint-disable max-len */
63
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 0, group: 'diagnostics'},
64
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 0, group: 'diagnostics'},
65
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 0, group: 'diagnostics'},
66
+ /* eslint-enable max-len */
59
67
  ],
60
68
  };
61
69
 
62
70
  const root = render(
63
- <SummaryTooltip category={category} gatherMode="snapshot"/>,
71
+ <SummaryTooltip category={category} gatherMode="snapshot" url="https://example.com"/>,
64
72
  {wrapper}
65
73
  );
66
74
 
@@ -68,6 +76,7 @@ describe('SummaryTooltip', () => {
68
76
  expect(() => root.getByText(/^[0-9]+$/)).toThrow();
69
77
  expect(root.getByText('2 audits passed')).toBeTruthy();
70
78
  expect(root.getByText('3 passable audits')).toBeTruthy();
79
+ expect(root.getByText('https://example.com'));
71
80
  });
72
81
 
73
82
  it('renders scored category tooltip with score', async () => {
@@ -75,14 +84,16 @@ describe('SummaryTooltip', () => {
75
84
  id: 'performance',
76
85
  score: 1,
77
86
  auditRefs: [
78
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
79
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
80
- {result: {score: 0, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
87
+ /* eslint-disable max-len */
88
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
89
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
90
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
91
+ /* eslint-enable max-len */
81
92
  ],
82
93
  };
83
94
 
84
95
  const root = render(
85
- <SummaryTooltip category={category} gatherMode="navigation"/>,
96
+ <SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
86
97
  {wrapper}
87
98
  );
88
99
 
@@ -90,6 +101,7 @@ describe('SummaryTooltip', () => {
90
101
  expect(root.getByText('100')).toBeTruthy();
91
102
  expect(root.getByText('2 audits passed')).toBeTruthy();
92
103
  expect(root.getByText('3 passable audits')).toBeTruthy();
104
+ expect(root.getByText('https://example.com'));
93
105
  });
94
106
 
95
107
  it('renders informative audit count if any', async () => {
@@ -97,14 +109,16 @@ describe('SummaryTooltip', () => {
97
109
  id: 'performance',
98
110
  score: 1,
99
111
  auditRefs: [
100
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
101
- {result: {score: 1, scoreDisplayMode: 'binary'}, weight: 1, group: 'diagnostics'},
102
- {result: {score: 0, scoreDisplayMode: 'informative'}, weight: 1, group: 'diagnostics'},
112
+ /* eslint-disable max-len */
113
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
114
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1, group: 'diagnostics'},
115
+ {result: {score: null, scoreDisplayMode: 'informative', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
116
+ /* eslint-enable max-len */
103
117
  ],
104
118
  };
105
119
 
106
120
  const root = render(
107
- <SummaryTooltip category={category} gatherMode="navigation"/>,
121
+ <SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
108
122
  {wrapper}
109
123
  );
110
124
 
@@ -113,5 +127,83 @@ describe('SummaryTooltip', () => {
113
127
  expect(root.getByText('2 audits passed')).toBeTruthy();
114
128
  expect(root.getByText('2 passable audits')).toBeTruthy();
115
129
  expect(root.getByText('1 informative audit')).toBeTruthy();
130
+ expect(root.getByText('https://example.com'));
131
+ });
132
+
133
+ it('renders highest impact audits', async () => {
134
+ const category: any = {
135
+ id: 'seo',
136
+ score: 1,
137
+ auditRefs: [
138
+ /* eslint-disable max-len */
139
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'group'},
140
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 2, group: 'group'},
141
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 3'}, weight: 3, group: 'group'},
142
+ /* eslint-enable max-len */
143
+ ],
144
+ };
145
+
146
+ const root = render(
147
+ <SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
148
+ {wrapper}
149
+ );
150
+
151
+ const audits = root.getAllByText(/^Audit [0-9]$/);
152
+
153
+ expect(root.getByText('Highest impact')).toBeTruthy();
154
+ expect(audits.map(a => a.textContent)).toEqual([
155
+ 'Audit 3',
156
+ 'Audit 2',
157
+ ]);
158
+ });
159
+
160
+ it('renders highest impact audits in performance', async () => {
161
+ const category: any = {
162
+ id: 'performance',
163
+ score: 0.75,
164
+ auditRefs: [
165
+ /* eslint-disable max-len */
166
+ {result: {score: 0.75, scoreDisplayMode: 'numeric', title: 'Metric 1'}, weight: 1, group: 'metrics'},
167
+ {result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 1', details: {type: 'opportunity', overallSavingsMs: 500}}, weight: 0, group: 'opportunities'},
168
+ {result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 2', details: {type: 'opportunity', overallSavingsMs: 1000}}, weight: 0, group: 'opportunities'},
169
+ {result: {score: 0, scoreDisplayMode: 'numeric', title: 'Audit 3', details: {type: 'opportunity', overallSavingsMs: 100}}, weight: 0, group: 'opportunities'},
170
+ /* eslint-enable max-len */
171
+ ],
172
+ };
173
+
174
+ const root = render(
175
+ <SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
176
+ {wrapper}
177
+ );
178
+
179
+ const audits = root.getAllByText(/^(Audit|Metric) [0-9]$/);
180
+
181
+ expect(root.getByText('Highest impact')).toBeTruthy();
182
+ expect(audits.map(a => a.textContent)).toEqual([
183
+ 'Audit 2',
184
+ 'Audit 1',
185
+ ]);
186
+ });
187
+
188
+ it('hides highest impact if nothing to show', async () => {
189
+ const category: any = {
190
+ id: 'performance',
191
+ score: 1,
192
+ auditRefs: [
193
+ /* eslint-disable max-len */
194
+ {result: {score: 1, scoreDisplayMode: 'binary', title: 'Audit 1'}, weight: 1, group: 'diagnostics'},
195
+ {result: {score: 0, scoreDisplayMode: 'binary', title: 'Audit 2'}, weight: 1},
196
+ {result: {score: null, scoreDisplayMode: 'informative', title: 'Audit 3'}, weight: 1, group: 'diagnostics'},
197
+ /* eslint-enable max-len */
198
+ ],
199
+ };
200
+
201
+ const root = render(
202
+ <SummaryTooltip category={category} gatherMode="navigation" url="https://example.com"/>,
203
+ {wrapper}
204
+ );
205
+
206
+ expect(() => root.getByText('Highest impact')).toThrow();
207
+ expect(() => root.getByText(/^Audit [0-9]$/)).toThrow();
116
208
  });
117
209
  });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+
7
+ import {render} from '@testing-library/preact';
8
+ import {FunctionComponent} from 'preact';
9
+
10
+ import {Markdown} from '../../src/wrappers/markdown';
11
+ import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
12
+
13
+ let wrapper: FunctionComponent;
14
+
15
+ beforeEach(() => {
16
+ wrapper = ({children}) => (
17
+ <ReportRendererProvider>
18
+ {children}
19
+ </ReportRendererProvider>
20
+ );
21
+ });
22
+
23
+ describe('Markdown', () => {
24
+ it('renders markdown text', () => {
25
+ const root = render(<Markdown text="Some `fancy` text"/>, {wrapper});
26
+ const text = root.getByText(/^Some.*text$/);
27
+ expect(text.innerHTML).toEqual('Some <code>fancy</code> text');
28
+ });
29
+ });
package/jest.config.js CHANGED
@@ -13,8 +13,8 @@ module.exports = {
13
13
  '**/lighthouse-cli/**/*-test.js',
14
14
  '**/report/**/*-test.js',
15
15
  '**/lighthouse-core/test/fraggle-rock/**/*-test-pptr.js',
16
- '**/lighthouse-treemap/**/*-test.js',
17
- '**/lighthouse-viewer/**/*-test.js',
16
+ '**/treemap/**/*-test.js',
17
+ '**/viewer/**/*-test.js',
18
18
  '**/third-party/**/*-test.js',
19
19
  '**/clients/test/**/*-test.js',
20
20
  '**/shared/**/*-test.js',
@@ -103,6 +103,8 @@ class ModernImageFormats extends ByteEfficiencyAudit {
103
103
  const items = [];
104
104
  const warnings = [];
105
105
  for (const image of images) {
106
+ const imageElement = imageElementsByURL.get(image.url);
107
+
106
108
  if (image.failed) {
107
109
  warnings.push(`Unable to decode ${URL.getURLDisplayName(image.url)}`);
108
110
  continue;
@@ -120,7 +122,6 @@ class ModernImageFormats extends ByteEfficiencyAudit {
120
122
  let fromProtocol = true;
121
123
 
122
124
  if (typeof webpSize === 'undefined') {
123
- const imageElement = imageElementsByURL.get(image.url);
124
125
  if (!imageElement) {
125
126
  warnings.push(`Unable to locate resource ${URL.getURLDisplayName(image.url)}`);
126
127
  continue;
@@ -155,6 +156,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
155
156
  const isCrossOrigin = !URL.originsMatch(pageURL, image.url);
156
157
 
157
158
  items.push({
159
+ node: imageElement ? ByteEfficiencyAudit.makeNodeItem(imageElement.node) : undefined,
158
160
  url,
159
161
  fromProtocol,
160
162
  isCrossOrigin,
@@ -166,7 +168,7 @@ class ModernImageFormats extends ByteEfficiencyAudit {
166
168
 
167
169
  /** @type {LH.Audit.Details.Opportunity['headings']} */
168
170
  const headings = [
169
- {key: 'url', valueType: 'thumbnail', label: ''},
171
+ {key: 'node', valueType: 'node', label: ''},
170
172
  {key: 'url', valueType: 'url', label: str_(i18n.UIStrings.columnURL)},
171
173
  {key: 'totalBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnResourceSize)},
172
174
  {key: 'wastedBytes', valueType: 'bytes', label: str_(i18n.UIStrings.columnWastedBytes)},