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.
- package/dist/report/bundle.esm.js +18 -3
- package/dist/report/flow.js +88 -32
- package/dist/report/standalone.js +2 -2
- package/flow-report/assets/standalone-flow-template.html +2 -4
- package/flow-report/src/app.tsx +7 -10
- package/flow-report/src/topbar.tsx +4 -7
- package/flow-report/src/util.ts +28 -1
- package/flow-report/src/wrappers/category-score.tsx +9 -24
- package/flow-report/src/wrappers/markdown.tsx +4 -13
- package/flow-report/src/wrappers/report.tsx +25 -57
- package/flow-report/standalone-flow.tsx +2 -2
- package/flow-report/test/setup/env-setup.ts +11 -5
- package/flow-report/test/summary/category-test.tsx +3 -6
- package/flow-report/test/summary/summary-test.tsx +3 -6
- package/flow-report/test/topbar-test.tsx +14 -14
- package/flow-report/test/util-test.tsx +38 -1
- package/flow-report/test/wrappers/category-score-test.tsx +3 -6
- package/flow-report/test/wrappers/markdown-test.tsx +1 -13
- package/lighthouse-core/lib/statistics.js +26 -39
- package/package.json +1 -1
- package/report/renderer/api.js +35 -1
- package/report/renderer/category-renderer.js +17 -3
- package/report/renderer/report-renderer.js +1 -0
- package/report/test/renderer/category-renderer-test.js +28 -0
- package/report/test/renderer/report-renderer-test.js +29 -0
- package/report/types/report-renderer.d.ts +5 -0
- package/flow-report/src/wrappers/report-renderer.tsx +0 -53
|
@@ -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){
|
|
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
|
|
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>
|
package/flow-report/src/app.tsx
CHANGED
|
@@ -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
|
-
<
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 {
|
|
15
|
+
import {saveFile} from '../../report/renderer/api';
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
|
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}} />
|
package/flow-report/src/util.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 {
|
|
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
|
|
14
|
-
|
|
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 {
|
|
11
|
-
import {
|
|
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
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
//
|
|
27
|
+
// Functions not implemented in JSDOM.
|
|
23
28
|
window.Element.prototype.scrollIntoView = jest.fn();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
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"
|
|
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
|
});
|