lighthouse 9.0.0-dev.20211117 → 9.0.0-dev.20211121

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.
@@ -47,7 +47,7 @@ class s{constructor(e,t){this._document=e,this._lighthouseChannel="unknown",this
47
47
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
48
48
  * See the License for the specific language governing permissions and
49
49
  * limitations under the License.
50
- */class c{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.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");this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,t.appendChild(n);let r=null;return e.description&&(r=this.dom.convertMarkdownLinkSnippets(e.description),r.classList.add("lh-audit-group__description","lh-audit-group__footer"),t.appendChild(r)),[t,r]}_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,l]=this.renderAuditGroup(n);for(const e of i)a.insertBefore(this.renderAudit(e),l);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","");const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];this.dom.find(".lh-audit-group__title",i).textContent=a;this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const s=t.map(this.renderAudit.bind(this));o.append(...s);const c=this.dom.find(".lh-audit-group",r);if(n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description","lh-audit-group__footer"),c.appendChild(e)}return this.dom.find(".lh-clump-toggletext--show",c).textContent=l.i18n.strings.show,this.dom.find(".lh-clump-toggletext--hide",c).textContent=l.i18n.strings.hide,o.classList.add(`lh-clump--${e.toLowerCase()}`),c}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}}
50
+ */class c{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.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");this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,t.appendChild(n);let r=null;return e.description&&(r=this.dom.convertMarkdownLinkSnippets(e.description),r.classList.add("lh-audit-group__description","lh-audit-group__footer"),t.appendChild(r)),[t,r]}_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,l]=this.renderAuditGroup(n);for(const e of i)a.insertBefore(this.renderAudit(e),l);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","");const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];this.dom.find(".lh-audit-group__title",i).textContent=a;this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const s=t.map(this.renderAudit.bind(this));o.append(...s);const c=this.dom.find(".lh-audit-group",r);if(n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description","lh-audit-group__footer"),c.appendChild(e)}return this.dom.find(".lh-clump-toggletext--show",c).textContent=l.i18n.strings.show,this.dom.find(".lh-clump-toggletext--hide",c).textContent=l.i18n.strings.hide,o.classList.add(`lh-clump--${e.toLowerCase()}`),c}renderCategoryScore(e,t,n){let r;if(r=n&&l.shouldDisplayAsFraction(n.gatherMode)?this.renderCategoryFraction(e):this.renderScoreGauge(e,t),n?.omitLabel){this.dom.find(".lh-gauge__label,.lh-fraction__label",r).remove()}if(n?.onPageAnchorRendered){const e=this.dom.find("a",r);n.onPageAnchorRendered(e)}return r}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}}
51
51
  /**
52
52
  * @license
53
53
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -145,7 +145,7 @@ class s{constructor(e,t){this._document=e,this._lighthouseChannel="unknown",this
145
145
  *
146
146
  * Dummy text for ensuring report robustness: <\/script> pre$`post %%LIGHTHOUSE_JSON%%
147
147
  * (this is handled by terser)
148
- */class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()}))),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
148
+ */class N{constructor(e){this._dom=e,this._opts={}}renderReport(e,t,n){if(!this._dom.rootEl&&t){console.warn("Please adopt the new report API in renderer/api.js.");const e=t.closest(".lh-root");e?this._dom.rootEl=e:(t.classList.add("lh-root","lh-vars"),this._dom.rootEl=t)}else this._dom.rootEl&&t&&(this._dom.rootEl=t);n&&(this._opts=n),this._dom.setLighthouseChannel(e.configSettings.channel||"unknown");const r=l.prepareReportResult(e);return this._dom.rootEl.textContent="",this._dom.rootEl.appendChild(this._renderReport(r)),this._dom.rootEl}_renderReportTopbar(e){const t=this._dom.createComponent("topbar"),n=this._dom.find("a.lh-topbar__url",t);return n.textContent=e.finalUrl,n.title=e.finalUrl,this._dom.safelySetHref(n,e.finalUrl),t}_renderReportHeader(){const e=this._dom.createComponent("heading"),t=this._dom.createComponent("scoresWrapper");return this._dom.find(".lh-scores-wrapper-placeholder",e).replaceWith(t),e}_renderReportFooter(e){const t=this._dom.createComponent("footer");return this._renderMetaBlock(e,t),this._dom.find(".lh-footer__version_issue",t).textContent=l.i18n.strings.footerIssue,this._dom.find(".lh-footer__version",t).textContent=e.lighthouseVersion,t}_renderMetaBlock(e,t){const n=l.getEmulationDescriptions(e.configSettings||{}),r=e.userAgent.match(/(\w*Chrome\/[\d.]+)/),o=Array.isArray(r)?r[1].replace("/"," ").replace("Chrome","Chromium"):"Chromium",i=e.configSettings.channel,a=e.environment.benchmarkIndex.toFixed(0),s=e.environment.credits?.["axe-core"],c=[["date",`Captured at ${l.i18n.formatDateTime(e.fetchTime)}`],["devices",`${n.deviceEmulation} with Lighthouse ${e.lighthouseVersion}`,`${l.i18n.strings.runtimeSettingsBenchmark}: ${a}\n${l.i18n.strings.runtimeSettingsCPUThrottling}: ${n.cpuThrottling}`+(s?`\n${l.i18n.strings.runtimeSettingsAxeVersion}: ${s}`:"")],["samples-one",l.i18n.strings.runtimeSingleLoad,l.i18n.strings.runtimeSingleLoadTooltip],["stopwatch",l.i18n.strings.runtimeAnalysisWindow],["networkspeed",`${n.summary}`,`${l.i18n.strings.runtimeSettingsNetworkThrottling}: ${n.networkThrottling}`],["chrome",`Using ${o}`+(i?` with ${i}`:""),`${l.i18n.strings.runtimeSettingsUANetwork}: "${e.environment.networkUserAgent}"`]],d=this._dom.find(".lh-meta__items",t);for(const[e,t,n]of c){const r=this._dom.createChildOf(d,"li","lh-meta__item");if(r.textContent=t,n){r.classList.add("lh-tooltip-boundary");this._dom.createChildOf(r,"div","lh-tooltip").textContent=n}r.classList.add("lh-report-icon",`lh-report-icon--${e}`)}}_renderReportWarnings(e){if(!e.runWarnings||0===e.runWarnings.length)return this._dom.createElement("div");const t=this._dom.createComponent("warningsToplevel");this._dom.find(".lh-warnings__msg",t).textContent=l.i18n.strings.toplevelWarningsMessage;const n=this._dom.find("ul",t);for(const t of e.runWarnings){n.appendChild(this._dom.createElement("li")).appendChild(this._dom.convertMarkdownLinkSnippets(t))}return t}_renderScoreGauges(e,t,n){const r=[],o=[],i=[];for(const a of Object.values(e.categories)){const s=n[a.id]||t,c=s.renderCategoryScore(a,e.categoryGroups||{},{gatherMode:e.gatherMode}),d=this._dom.find("a.lh-gauge__wrapper, a.lh-fraction__wrapper",c);d&&(this._dom.safelySetHref(d,`#${a.id}`),d.addEventListener("click",(e=>{if(!d.matches('[href^="#"]'))return;const t=d.getAttribute("href"),n=this._dom.rootEl;if(!t||!n)return;const r=this._dom.find(t,n);e.preventDefault(),r.scrollIntoView()})),this._opts.onPageAnchorRendered?.(d)),l.isPluginCategory(a.id)?i.push(c):s.renderCategoryScore===t.renderCategoryScore?r.push(c):o.push(c)}return[...r,...o,...i]}_renderReport(e){const t=new L(e.configSettings.locale,{...l.UIStrings,...e.i18n.rendererFormattedStrings});l.i18n=t,l.reportJson=e;const n=e.audits["full-page-screenshot"]&&e.audits["full-page-screenshot"].details&&"full-page-screenshot"===e.audits["full-page-screenshot"].details.type?e.audits["full-page-screenshot"].details:void 0,r=new z(this._dom,{fullPageScreenshot:n}),o=new c(this._dom,r),i={performance:new M(this._dom,r),pwa:new F(this._dom,r)},a=this._dom.createElement("div");a.appendChild(this._renderReportHeader());const s=this._dom.createElement("div","lh-container"),d=this._dom.createElement("div","lh-report");let h;d.appendChild(this._renderReportWarnings(e));1===Object.keys(e.categories).length?a.classList.add("lh-header--solo-category"):h=this._dom.createElement("div","lh-scores-header");const p=this._dom.createElement("div");if(p.classList.add("lh-scorescale-wrap"),p.append(this._dom.createComponent("scorescale")),h){const t=this._dom.find(".lh-scores-container",a);h.append(...this._renderScoreGauges(e,o,i)),t.appendChild(h),t.appendChild(p);const n=this._dom.createElement("div","lh-sticky-header");n.append(...this._renderScoreGauges(e,o,i)),s.appendChild(n)}const u=d.appendChild(this._dom.createElement("div","lh-categories")),g={gatherMode:e.gatherMode};for(const t of Object.values(e.categories)){const n=i[t.id]||o;n.dom.createChildOf(u,"div","lh-category-wrapper").appendChild(n.render(t,e.categoryGroups,g))}o.injectFinalScreenshot(u,e.audits,p);const m=this._dom.createFragment();return m.append(this._dom.createComponent("styles")),this._opts.omitTopbar||m.appendChild(this._renderReportTopbar(e)),m.appendChild(s),s.appendChild(a),s.appendChild(d),d.appendChild(this._renderReportFooter(e)),n&&A.installFullPageScreenshot(this._dom.rootEl,n.screenshot),m}}
149
149
  /**
150
150
  * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
151
151
  * 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
@@ -28,12 +28,10 @@ limitations under the License.
28
28
  <body>
29
29
  <noscript>Lighthouse report requires JavaScript. Please enable.</noscript>
30
30
 
31
- <main class="flow-vars lh-root lh-vars"><!-- report populated here --></main>
31
+ <main><!-- report populated here --></main>
32
32
 
33
33
  <script>window.__LIGHTHOUSE_FLOW_JSON__ = %%LIGHTHOUSE_FLOW_JSON%%;</script>
34
- <script>%%LIGHTHOUSE_FLOW_JAVASCRIPT%%
35
- __initLighthouseFlowReport__();
36
- </script>
34
+ <script>%%LIGHTHOUSE_FLOW_JAVASCRIPT%%</script>
37
35
  <script>console.log('window.__LIGHTHOUSE_FLOW_JSON__', __LIGHTHOUSE_FLOW_JSON__);</script>
38
36
  </body>
39
37
  </html>
@@ -7,7 +7,6 @@
7
7
  import {FunctionComponent} from 'preact';
8
8
  import {useLayoutEffect, useRef, useState} from 'preact/hooks';
9
9
 
10
- import {ReportRendererProvider} from './wrappers/report-renderer';
11
10
  import {Sidebar} from './sidebar/sidebar';
12
11
  import {Summary} from './summary/summary';
13
12
  import {classNames, FlowResultContext, useHashState} from './util';
@@ -52,15 +51,13 @@ export const App: FunctionComponent<{flowResult: LH.FlowResult}> = ({flowResult}
52
51
  const [collapsed, setCollapsed] = useState(false);
53
52
  return (
54
53
  <FlowResultContext.Provider value={flowResult}>
55
- <ReportRendererProvider>
56
- <I18nProvider>
57
- <div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
58
- <Topbar onMenuClick={() => setCollapsed(c => !c)} />
59
- <Sidebar/>
60
- <Content/>
61
- </div>
62
- </I18nProvider>
63
- </ReportRendererProvider>
54
+ <I18nProvider>
55
+ <div className={classNames('App', {'App--collapsed': collapsed})} data-testid="App">
56
+ <Topbar onMenuClick={() => setCollapsed(c => !c)} />
57
+ <Sidebar/>
58
+ <Content/>
59
+ </div>
60
+ </I18nProvider>
64
61
  </FlowResultContext.Provider>
65
62
  );
66
63
  };
@@ -12,11 +12,9 @@ import {getFilenamePrefix} from '../../report/generator/file-namer';
12
12
  import {useLocalizedStrings} from './i18n/i18n';
13
13
  import {HamburgerIcon, InfoIcon} from './icons';
14
14
  import {useFlowResult} from './util';
15
- import {useReportRenderer} from './wrappers/report-renderer';
15
+ import {saveFile} from '../../report/renderer/api';
16
16
 
17
- import type {DOM} from '../../report/renderer/dom';
18
-
19
- function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
17
+ function saveHtml(flowResult: LH.FlowResult) {
20
18
  const htmlStr = document.documentElement.outerHTML;
21
19
  const blob = new Blob([htmlStr], {type: 'text/html'});
22
20
 
@@ -24,7 +22,7 @@ function saveHtml(flowResult: LH.FlowResult, dom: DOM) {
24
22
  const name = flowResult.name.replace(/\s/g, '-');
25
23
  const filename = getFilenamePrefix(name, lhr.fetchTime);
26
24
 
27
- dom.saveFile(blob, filename);
25
+ saveFile(blob, filename);
28
26
  }
29
27
 
30
28
  /* eslint-disable max-len */
@@ -78,7 +76,6 @@ const TopbarButton: FunctionComponent<{
78
76
  export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLButtonElement>}> =
79
77
  ({onMenuClick}) => {
80
78
  const flowResult = useFlowResult();
81
- const {dom} = useReportRenderer();
82
79
  const strings = useLocalizedStrings();
83
80
  const [showHelpDialog, setShowHelpDialog] = useState(false);
84
81
 
@@ -92,7 +89,7 @@ export const Topbar: FunctionComponent<{onMenuClick: JSX.MouseEventHandler<HTMLB
92
89
  </div>
93
90
  <div className="Topbar__title">{strings.title}</div>
94
91
  <TopbarButton
95
- onClick={() => saveHtml(flowResult, dom)}
92
+ onClick={() => saveHtml(flowResult)}
96
93
  label="Button that saves the report as HTML"
97
94
  >{strings.save}</TopbarButton>
98
95
  <div style={{flexGrow: 1}} />
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import {createContext} from 'preact';
8
- import {useContext, useEffect, useMemo, useState} from 'preact/hooks';
8
+ import {useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'preact/hooks';
9
9
 
10
10
  import type {UIStringsType} from './i18n/ui-strings';
11
11
 
@@ -121,6 +121,32 @@ function useHashState(): LH.FlowResult.HashState|null {
121
121
  }, [indexString, flowResult, anchor]);
122
122
  }
123
123
 
124
+ /**
125
+ * Creates a DOM subtree from non-preact code (e.g. LH report renderer).
126
+ * @param renderCallback Callback that renders a DOM subtree.
127
+ * @param inputs Changes to these values will trigger a re-render of the DOM subtree.
128
+ * @return Reference to the element that will contain the DOM subtree.
129
+ */
130
+ function useExternalRenderer<T extends Element>(
131
+ renderCallback: () => Node,
132
+ inputs?: ReadonlyArray<unknown>
133
+ ) {
134
+ const ref = useRef<T>(null);
135
+
136
+ useLayoutEffect(() => {
137
+ if (!ref.current) return;
138
+
139
+ const root = renderCallback();
140
+ ref.current.appendChild(root);
141
+
142
+ return () => {
143
+ if (ref.current?.contains(root)) ref.current.removeChild(root);
144
+ };
145
+ }, inputs);
146
+
147
+ return ref;
148
+ }
149
+
124
150
  export {
125
151
  FlowResultContext,
126
152
  classNames,
@@ -131,4 +157,5 @@ export {
131
157
  useFlowResult,
132
158
  useHashParams,
133
159
  useHashState,
160
+ useExternalRenderer,
134
161
  };
@@ -5,37 +5,22 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
- import {useEffect, useLayoutEffect, useRef} from 'preact/hooks';
9
8
 
10
- import {useReportRenderer} from './report-renderer';
9
+ import {renderCategoryScore} from '../../../report/renderer/api';
10
+ import {useExternalRenderer} from '../util';
11
11
 
12
12
  export const CategoryScore: FunctionComponent<{
13
13
  category: LH.ReportResult.Category,
14
14
  href: string,
15
15
  gatherMode: LH.Result.GatherMode,
16
16
  }> = ({category, href, gatherMode}) => {
17
- const {categoryRenderer} = useReportRenderer();
18
- const ref = useRef<HTMLDivElement>(null);
19
-
20
- useLayoutEffect(() => {
21
- const el = categoryRenderer.renderCategoryScore(category, {}, {gatherMode});
22
-
23
- // Category label is displayed in the navigation header.
24
- const label = el.querySelector('.lh-gauge__label,.lh-fraction__label');
25
- if (label) label.remove();
26
-
27
- if (ref.current) ref.current.append(el);
28
- return () => {
29
- if (ref.current && ref.current.contains(el)) {
30
- ref.current.removeChild(el);
31
- }
32
- };
33
- }, [categoryRenderer, category]);
34
-
35
- useEffect(() => {
36
- const anchor = ref.current && ref.current.querySelector('a') as HTMLAnchorElement;
37
- if (anchor) anchor.href = href;
38
- }, [href]);
17
+ const ref = useExternalRenderer<HTMLDivElement>(() => {
18
+ return renderCategoryScore(category, {
19
+ gatherMode,
20
+ omitLabel: true,
21
+ onPageAnchorRendered: link => link.href = href,
22
+ });
23
+ }, [category, href]);
39
24
 
40
25
  return (
41
26
  <div ref={ref} data-testid="CategoryScore"/>
@@ -5,22 +5,13 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
- import {useLayoutEffect, useRef} from 'preact/hooks';
9
8
 
10
- import {useReportRenderer} from '../wrappers/report-renderer';
9
+ import {convertMarkdownCodeSnippets} from '../../../report/renderer/api';
10
+ import {useExternalRenderer} from '../util';
11
11
 
12
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
- };
13
+ const ref = useExternalRenderer<HTMLSpanElement>(() => {
14
+ return convertMarkdownCodeSnippets(text);
24
15
  }, [text]);
25
16
 
26
17
  return <span ref={ref}/>;
@@ -5,73 +5,41 @@
5
5
  */
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
- import {useLayoutEffect, useRef} from 'preact/hooks';
9
8
 
10
- import {ElementScreenshotRenderer} from '../../../report/renderer/element-screenshot-renderer';
11
- import {getFullPageScreenshot} from '../util';
12
- import {useReportRenderer} from './report-renderer';
9
+ import {renderReport} from '../../../report/renderer/api.js';
10
+ import {useExternalRenderer} from '../util';
13
11
 
14
12
  /**
15
13
  * The default behavior of anchor links is not compatible with the flow report's hash navigation.
16
- * This function converts any anchor links under the provided element to a flow report link.
14
+ * This function converts a category score anchor link to a flow report link.
17
15
  * e.g. <a href="#link"> -> <a href="#index=0&anchor=link">
18
16
  */
19
- function convertChildAnchors(element: HTMLElement, index: number) {
20
- const links = element.querySelectorAll('a[href]') as NodeListOf<HTMLAnchorElement>;
21
- for (const link of links) {
22
- // Check if the link destination is in the report.
23
- const currentUrl = new URL(location.href);
24
- currentUrl.hash = '';
25
- currentUrl.search = '';
26
- const linkUrl = new URL(link.href);
27
- linkUrl.hash = '';
28
- linkUrl.search = '';
29
- if (currentUrl.href !== linkUrl.href || !link.hash) continue;
30
-
31
- const nodeId = link.hash.substr(1);
32
- link.hash = `#index=${index}&anchor=${nodeId}`;
33
- link.onclick = e => {
34
- e.preventDefault();
35
- const el = document.getElementById(nodeId);
36
- if (el) el.scrollIntoView();
37
- };
38
- }
17
+ function convertAnchor(link: HTMLAnchorElement, index: number) {
18
+ // Clear existing event listeners by cloning node.
19
+ const newLink = link.cloneNode(true) as HTMLAnchorElement;
20
+ if (!newLink.hash) return newLink;
21
+
22
+ const nodeId = link.hash.substr(1);
23
+ newLink.hash = `#index=${index}&anchor=${nodeId}`;
24
+ newLink.onclick = e => {
25
+ e.preventDefault();
26
+ const el = document.getElementById(nodeId);
27
+ if (el) el.scrollIntoView();
28
+ };
29
+ link.replaceWith(newLink);
39
30
  }
40
31
 
41
- const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
32
+ export const Report: FunctionComponent<{hashState: LH.FlowResult.HashState}> =
42
33
  ({hashState}) => {
43
- const {dom, reportRenderer} = useReportRenderer();
44
- const ref = useRef<HTMLDivElement>(null);
45
-
46
- useLayoutEffect(() => {
47
- if (ref.current) {
48
- dom.clearComponentCache();
49
- reportRenderer.renderReport(hashState.currentLhr, ref.current);
50
- convertChildAnchors(ref.current, hashState.index);
51
- const fullPageScreenshot = getFullPageScreenshot(hashState.currentLhr);
52
- if (fullPageScreenshot) {
53
- ElementScreenshotRenderer.installOverlayFeature({
54
- dom,
55
- rootEl: ref.current,
56
- overlayContainerEl: ref.current,
57
- fullPageScreenshot,
58
- });
59
- }
60
- const topbar = ref.current.querySelector('.lh-topbar');
61
- if (topbar) topbar.remove();
62
- }
63
-
64
- return () => {
65
- if (ref.current) ref.current.textContent = '';
66
- };
67
- }, [reportRenderer, hashState]);
34
+ const ref = useExternalRenderer<HTMLDivElement>(() => {
35
+ return renderReport(hashState.currentLhr, {
36
+ disableAutoDarkModeAndFireworks: true,
37
+ omitTopbar: true,
38
+ onPageAnchorRendered: link => convertAnchor(link, hashState.index),
39
+ });
40
+ }, [hashState]);
68
41
 
69
42
  return (
70
- <div ref={ref} className="lh-root" data-testid="Report"/>
43
+ <div ref={ref} data-testid="Report"/>
71
44
  );
72
45
  };
73
-
74
- export {
75
- convertChildAnchors,
76
- Report,
77
- };
@@ -13,12 +13,12 @@ import {render} from 'preact';
13
13
 
14
14
  import {App} from './src/app';
15
15
 
16
- // Used by standalone-flow.html
17
16
  function __initLighthouseFlowReport__() {
18
- // TODO(adamraine): add lh-vars, etc classes programmatically instead of in the HTML template
19
17
  const container = document.body.querySelector('main');
20
18
  if (!container) throw Error('Container element not found');
19
+ container.classList.add('flow-vars', 'lh-root', 'lh-vars');
21
20
  render(<App flowResult={window.__LIGHTHOUSE_FLOW_JSON__} />, container);
22
21
  }
23
22
 
24
23
  window.__initLighthouseFlowReport__ = __initLighthouseFlowReport__;
24
+ window.__initLighthouseFlowReport__();
@@ -9,18 +9,24 @@ import {JSDOM} from 'jsdom';
9
9
 
10
10
  /**
11
11
  * The jest environment "jsdom" does not work when preact is combined with the report renderer.
12
+ * This sets up our own environment with JSDOM globals.
12
13
  */
13
- function setupJsDom() {
14
+ beforeEach(() => {
14
15
  const {window} = new JSDOM(undefined, {
15
16
  url: 'file:///Users/example/report.html/',
16
17
  });
17
18
  global.window = window as any;
18
19
  global.document = window.document;
19
20
  global.location = window.location;
21
+ global.self = global.window;
22
+
23
+ // Use JSDOM types as necessary.
20
24
  global.Blob = window.Blob;
25
+ global.HTMLInputElement = window.HTMLInputElement;
21
26
 
22
- // Function not implemented in JSDOM.
27
+ // Functions not implemented in JSDOM.
23
28
  window.Element.prototype.scrollIntoView = jest.fn();
24
- }
25
-
26
- global.beforeEach(setupJsDom);
29
+ global.self.matchMedia = jest.fn<any, any>(() => ({
30
+ addListener: jest.fn(),
31
+ }));
32
+ });
@@ -11,7 +11,6 @@ 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';
15
14
 
16
15
  let wrapper: FunctionComponent;
17
16
 
@@ -19,11 +18,9 @@ beforeEach(() => {
19
18
  // Include sample flowResult for locale in I18nProvider.
20
19
  wrapper = ({children}) => (
21
20
  <FlowResultContext.Provider value={flowResult}>
22
- <ReportRendererProvider>
23
- <I18nProvider>
24
- {children}
25
- </I18nProvider>
26
- </ReportRendererProvider>
21
+ <I18nProvider>
22
+ {children}
23
+ </I18nProvider>
27
24
  </FlowResultContext.Provider>
28
25
  );
29
26
  });
@@ -10,7 +10,6 @@ import {FunctionComponent} from 'preact';
10
10
  import {I18nProvider} from '../../src/i18n/i18n';
11
11
  import {SummaryHeader, SummaryFlowStep} from '../../src/summary/summary';
12
12
  import {FlowResultContext} from '../../src/util';
13
- import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
14
13
  import {flowResult} from '../sample-flow';
15
14
 
16
15
  let wrapper: FunctionComponent;
@@ -18,11 +17,9 @@ let wrapper: FunctionComponent;
18
17
  beforeEach(() => {
19
18
  wrapper = ({children}) => (
20
19
  <FlowResultContext.Provider value={flowResult}>
21
- <ReportRendererProvider>
22
- <I18nProvider>
23
- {children}
24
- </I18nProvider>
25
- </ReportRendererProvider>
20
+ <I18nProvider>
21
+ {children}
22
+ </I18nProvider>
26
23
  </FlowResultContext.Provider>
27
24
  );
28
25
  });
@@ -8,11 +8,19 @@ import {jest} from '@jest/globals';
8
8
  import {FunctionComponent} from 'preact';
9
9
  import {act, render} from '@testing-library/preact';
10
10
 
11
- import {Topbar} from '../src/topbar';
12
11
  import {FlowResultContext} from '../src/util';
13
- import {ReportRendererContext} from '../src/wrappers/report-renderer';
14
12
  import {I18nProvider} from '../src/i18n/i18n';
15
13
 
14
+ const mockSaveFile = jest.fn();
15
+ jest.unstable_mockModule('../../../report/renderer/api.js', () => ({
16
+ saveFile: mockSaveFile,
17
+ }));
18
+
19
+ let Topbar: typeof import('../src/topbar').Topbar;
20
+ beforeAll(async () => {
21
+ Topbar = (await import('../src/topbar')).Topbar;
22
+ });
23
+
16
24
  const flowResult = {
17
25
  name: 'User flow',
18
26
  steps: [{lhr: {
@@ -23,22 +31,14 @@ const flowResult = {
23
31
  } as any;
24
32
 
25
33
  let wrapper: FunctionComponent;
26
- let mockSaveFile = jest.fn();
27
34
 
28
35
  beforeEach(() => {
29
- mockSaveFile = jest.fn();
30
- const reportRendererValue: any = {
31
- dom: {
32
- saveFile: mockSaveFile,
33
- },
34
- };
36
+ mockSaveFile.mockReset();
35
37
  wrapper = ({children}) => (
36
38
  <FlowResultContext.Provider value={flowResult}>
37
- <ReportRendererContext.Provider value={reportRendererValue}>
38
- <I18nProvider>
39
- {children}
40
- </I18nProvider>
41
- </ReportRendererContext.Provider>
39
+ <I18nProvider>
40
+ {children}
41
+ </I18nProvider>
42
42
  </FlowResultContext.Provider>
43
43
  );
44
44
  });
@@ -5,11 +5,12 @@
5
5
  */
6
6
 
7
7
  import {jest} from '@jest/globals';
8
+ import {render} from '@testing-library/preact';
8
9
  import {renderHook} from '@testing-library/preact-hooks';
9
10
  import {FunctionComponent} from 'preact';
10
11
  import {act} from 'preact/test-utils';
11
12
 
12
- import {FlowResultContext, useHashState} from '../src/util';
13
+ import {FlowResultContext, useExternalRenderer, useHashState} from '../src/util';
13
14
  import {flowResult} from './sample-flow';
14
15
 
15
16
  let wrapper: FunctionComponent;
@@ -94,3 +95,39 @@ describe('useHashState', () => {
94
95
  expect(result.current).toBeNull();
95
96
  });
96
97
  });
98
+
99
+ describe('useExternalRenderer', () => {
100
+ it('attaches DOM subtree of render callback', () => {
101
+ const Container: FunctionComponent = () => {
102
+ const ref = useExternalRenderer<HTMLDivElement>(() => {
103
+ const el = document.createElement('div');
104
+ el.textContent = 'Some text';
105
+ return el;
106
+ });
107
+ return <div ref={ref}/>;
108
+ };
109
+
110
+ const root = render(<Container/>);
111
+
112
+ expect(root.getByText('Some text')).toBeTruthy();
113
+ });
114
+
115
+ it('re-renders DOM subtree when input changes', () => {
116
+ const Container: FunctionComponent<{text: string}> = ({text}) => {
117
+ const ref = useExternalRenderer<HTMLDivElement>(() => {
118
+ const el = document.createElement('div');
119
+ el.textContent = text;
120
+ return el;
121
+ }, [text]);
122
+ return <div ref={ref}/>;
123
+ };
124
+
125
+ const root = render(<Container text="Some text"/>);
126
+
127
+ expect(root.getByText('Some text')).toBeTruthy();
128
+
129
+ root.rerender(<Container text="New text"/>);
130
+
131
+ expect(root.getByText('New text')).toBeTruthy();
132
+ });
133
+ });
@@ -9,7 +9,6 @@ import {render} from '@testing-library/preact';
9
9
 
10
10
  import {CategoryScore} from '../../src/wrappers/category-score';
11
11
  import {FlowResultContext} from '../../src/util';
12
- import {ReportRendererProvider} from '../../src/wrappers/report-renderer';
13
12
  import {I18nProvider} from '../../src/i18n/i18n';
14
13
  import {flowResult} from '../sample-flow';
15
14
 
@@ -18,11 +17,9 @@ let wrapper: FunctionComponent;
18
17
  beforeEach(() => {
19
18
  wrapper = ({children}) => (
20
19
  <FlowResultContext.Provider value={flowResult}>
21
- <ReportRendererProvider>
22
- <I18nProvider>
23
- {children}
24
- </I18nProvider>
25
- </ReportRendererProvider>
20
+ <I18nProvider>
21
+ {children}
22
+ </I18nProvider>
26
23
  </FlowResultContext.Provider>
27
24
  );
28
25
  });
@@ -5,24 +5,12 @@
5
5
  */
6
6
 
7
7
  import {render} from '@testing-library/preact';
8
- import {FunctionComponent} from 'preact';
9
8
 
10
9
  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
10
 
23
11
  describe('Markdown', () => {
24
12
  it('renders markdown text', () => {
25
- const root = render(<Markdown text="Some `fancy` text"/>, {wrapper});
13
+ const root = render(<Markdown text="Some `fancy` text"/>);
26
14
  const text = root.getByText(/^Some.*text$/);
27
15
  expect(text.innerHTML).toEqual('Some <code>fancy</code> text');
28
16
  });