Welcome to this blog. This blog is created with Docusaurus 2 alpha.
This is a test post.
A whole bunch of other information.
diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..f1e7bedf --- /dev/null +++ b/404.html @@ -0,0 +1,17 @@ + + +
+ + +We could not find what you were looking for.
Please contact the owner of the site that linked you to the original URL and let them know their link is broken.
0&&(l=i.getRangeAt(0)),r.append(a),a.select(),a.selectionStart=0,a.selectionEnd=e.length;var c=!1;try{c=document.execCommand("copy")}catch(s){}a.remove(),l&&(i.removeAllRanges(),i.addRange(l)),o&&o.focus()}(t),i(!0),l.current=window.setTimeout((function(){i(!1)}),1e3)}),[t]);return(0,r.useEffect)((function(){return function(){return window.clearTimeout(l.current)}}),[]),r.createElement("button",{type:"button","aria-label":o?(0,K.I)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,K.I)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,K.I)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,u.Z)("clean-btn",n,U.copyButton,o&&U.copyButtonCopied),onClick:c},r.createElement("span",{className:U.copyButtonIcons,"aria-hidden":"true"},r.createElement("svg",{className:U.copyButtonIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})),r.createElement("svg",{className:U.copyButtonSuccessIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"}))))}var X="wordWrapButtonIcon_tV8z",$="wordWrapButtonEnabled_SVjJ";function J(e){var t=e.className,n=e.onClick,a=e.isEnabled,o=(0,K.I)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return r.createElement("button",{type:"button",onClick:n,className:(0,u.Z)("clean-btn",t,a&&$),"aria-label":o,title:o},r.createElement("svg",{className:X,viewBox:"0 0 24 24","aria-hidden":"true"},r.createElement("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})))}function Y(e){var t,n,a,i,l,c,s,m,f,v,h,g=e.children,b=e.className,E=void 0===b?"":b,k=e.metastring,C=e.title,Z=e.showLineNumbers,j=e.language,B=(0,d.L)().prism,O=B.defaultLanguage,L=B.magicComments,S=null!=(t=null!=j?j:null==(n=E.split(" ").find((function(e){return e.startsWith("language-")})))?void 0:n.replace(/language-/,""))?t:O,z=p(),P=(a=(0,r.useState)(!1),i=a[0],l=a[1],c=(0,r.useState)(!1),s=c[0],m=c[1],f=(0,r.useRef)(null),v=(0,r.useCallback)((function(){var e=f.current.querySelector("code");i?e.removeAttribute("style"):(e.style.whiteSpace="pre-wrap",e.style.overflowWrap="anywhere"),l((function(e){return!e}))}),[f,i]),h=(0,r.useCallback)((function(){var e=f.current,t=e.scrollWidth>e.clientWidth||f.current.querySelector("code").hasAttribute("style");m(t)}),[f]),_(f,h),(0,r.useEffect)((function(){h()}),[i,h]),(0,r.useEffect)((function(){return window.addEventListener("resize",h,{passive:!0}),function(){window.removeEventListener("resize",h)}}),[h]),{codeBlockRef:f,isEnabled:i,isCodeScrollable:s,toggle:v}),I=function(e){var t,n;return null!=(t=null==e||null==(n=e.match(y))?void 0:n.groups.title)?t:""}(k)||C,A=N(g,{metastring:k,language:S,magicComments:L}),D=A.lineClassNames,M=A.code,H=null!=Z?Z:function(e){return Boolean(null==e?void 0:e.includes("showLineNumbers"))}(k);return r.createElement(w,{as:"div",className:(0,u.Z)(E,S&&!E.includes("language-"+S)&&"language-"+S)},I&&r.createElement("div",{className:T.codeBlockTitle},I),r.createElement("div",{className:T.codeBlockContent},r.createElement(R,(0,o.Z)({},x,{theme:z,code:M,language:null!=S?S:"text"}),(function(e){var t=e.className,n=e.tokens,a=e.getLineProps,o=e.getTokenProps;return r.createElement("pre",{tabIndex:0,ref:P.codeBlockRef,className:(0,u.Z)(t,T.codeBlock,"thin-scrollbar")},r.createElement("code",{className:(0,u.Z)(T.codeBlockLines,H&&T.codeBlockLinesWithNumbering)},n.map((function(e,t){return r.createElement(F,{key:t,line:e,getLineProps:a,getTokenProps:o,classNames:D[t],showLineNumbers:H})}))))})),r.createElement("div",{className:T.buttonGroup},(P.isEnabled||P.isCodeScrollable)&&r.createElement(J,{className:T.codeButton,onClick:function(){return P.toggle()},isEnabled:P.isEnabled}),r.createElement(G,{className:T.codeButton,code:M}))))}var Q=["children"];function ee(e){var t=e.children,n=(0,i.Z)(e,Q),a=(0,s.Z)(),l=function(e){return r.Children.toArray(e).some((function(e){return(0,r.isValidElement)(e)}))?e:Array.isArray(e)?e.join(""):e}(t),c="string"==typeof l?Y:j;return r.createElement(c,(0,o.Z)({key:String(a)},n),l)}var te=n(8042);var ne=n(8594),re="details_XubN",ae="isBrowser_Zzsw",oe="collapsibleContent_NJ8j",ie=["summary","children"];function le(e){return!!e&&("SUMMARY"===e.tagName||le(e.parentElement))}function ce(e,t){return!!e&&(e===t||ce(e.parentElement,t))}function se(e){var t=e.summary,n=e.children,a=(0,i.Z)(e,ie),l=(0,s.Z)(),c=(0,r.useRef)(null),m=(0,ne.u)({initialState:!a.open}),d=m.collapsed,p=m.setCollapsed,f=(0,r.useState)(a.open),v=f[0],h=f[1];return r.createElement("details",(0,o.Z)({},a,{ref:c,open:v,"data-collapsed":d,className:(0,u.Z)(re,l&&ae,a.className),onMouseDown:function(e){le(e.target)&&e.detail>1&&e.preventDefault()},onClick:function(e){e.stopPropagation();var t=e.target;le(t)&&ce(t,c.current)&&(e.preventDefault(),d?(p(!1),h(!0)):p(!0))}}),null!=t?t:r.createElement("summary",null,"Details"),r.createElement(ne.z,{lazy:!1,collapsed:d,disableSSRStyle:!0,onCollapseTransitionEnd:function(e){p(e),h(!e)}},r.createElement("div",{className:oe},n)))}var ue="details_djZf";function me(e){var t=Object.assign({},e);return r.createElement(se,(0,o.Z)({},t,{className:(0,u.Z)("alert alert--info",ue,t.className)}))}var de=n(3668);function pe(e){return r.createElement(de.Z,e)}var fe="containsTaskList_PKEN";var ve="img_evYn";var he="admonition_DEdc",ge="admonitionHeading_UWCI",ye="admonitionIcon_fk7I",be="admonitionContent_Up4K";var Ee={note:{infimaClassName:"secondary",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"}))},label:r.createElement(K.Z,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)"},"note")},tip:{infimaClassName:"success",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)"},"tip")},danger:{infimaClassName:"danger",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"}))},label:r.createElement(K.Z,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)"},"danger")},info:{infimaClassName:"info",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)"},"info")},caution:{infimaClassName:"warning",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 16 16"},r.createElement("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"}))},label:r.createElement(K.Z,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)"},"caution")}},ke={secondary:"note",important:"info",success:"tip",warning:"danger"};function Ne(e){var t,n=function(e){var t=r.Children.toArray(e),n=t.find((function(e){var t;return r.isValidElement(e)&&"mdxAdmonitionTitle"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return{mdxAdmonitionTitle:n,rest:a}}(e.children),a=n.mdxAdmonitionTitle,o=n.rest;return Object.assign({},e,{title:null!=(t=e.title)?t:a,children:o})}var Ce={head:function(e){var t=r.Children.map(e.children,(function(e){return r.isValidElement(e)?function(e){var t;if(null!=(t=e.props)&&t.mdxType&&e.props.originalType){var n=e.props,a=(n.mdxType,n.originalType,(0,i.Z)(n,c));return r.createElement(e.props.originalType,a)}return e}(e):e}));return r.createElement(l.Z,e,t)},code:function(e){var t=["a","abbr","b","br","button","cite","code","del","dfn","em","i","img","input","ins","kbd","label","object","output","q","ruby","s","small","span","strong","sub","sup","time","u","var","wbr"];return r.Children.toArray(e.children).every((function(e){var n;return"string"==typeof e&&!e.includes("\n")||(0,r.isValidElement)(e)&&t.includes(null==(n=e.props)?void 0:n.mdxType)}))?r.createElement("code",e):r.createElement(ee,e)},a:function(e){return r.createElement(te.Z,e)},pre:function(e){var t;return r.createElement(ee,(0,r.isValidElement)(e.children)&&"code"===(null==(t=e.children.props)?void 0:t.originalType)?e.children.props:Object.assign({},e))},details:function(e){var t=r.Children.toArray(e.children),n=t.find((function(e){var t;return r.isValidElement(e)&&"summary"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return r.createElement(me,(0,o.Z)({},e,{summary:n}),a)},ul:function(e){return r.createElement("ul",(0,o.Z)({},e,{className:(t=e.className,(0,u.Z)(t,(null==t?void 0:t.includes("contains-task-list"))&&fe))}));var t},img:function(e){return r.createElement("img",(0,o.Z)({loading:"lazy"},e,{className:(t=e.className,(0,u.Z)(t,ve))}));var t},h1:function(e){return r.createElement(pe,(0,o.Z)({as:"h1"},e))},h2:function(e){return r.createElement(pe,(0,o.Z)({as:"h2"},e))},h3:function(e){return r.createElement(pe,(0,o.Z)({as:"h3"},e))},h4:function(e){return r.createElement(pe,(0,o.Z)({as:"h4"},e))},h5:function(e){return r.createElement(pe,(0,o.Z)({as:"h5"},e))},h6:function(e){return r.createElement(pe,(0,o.Z)({as:"h6"},e))},admonition:function(e){var t=Ne(e),n=t.children,a=t.type,o=t.title,i=t.icon,l=function(e){var t,n=null!=(t=ke[e])?t:e;return Ee[n]||(console.warn('No admonition config found for admonition type "'+n+'". Using Info as fallback.'),Ee.info)}(a),c=null!=o?o:l.label,s=l.iconComponent,m=null!=i?i:r.createElement(s,null);return r.createElement("div",{className:(0,u.Z)(f.k.common.admonition,f.k.common.admonitionType(e.type),"alert","alert--"+l.infimaClassName,he)},r.createElement("div",{className:ge},r.createElement("span",{className:ye},m),c),r.createElement("div",{className:be},n))},mermaid:function(){return null}};function Ze(e){var t=e.children;return r.createElement(a.Zo,{components:Ce},t)}},4621:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(3289),a=n(8795),o=n(8042);function i(e){var t=e.permalink,n=e.title,i=e.subLabel,l=e.isNext;return r.createElement(o.Z,{className:(0,a.Z)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t},i&&r.createElement("div",{className:"pagination-nav__sublabel"},i),r.createElement("div",{className:"pagination-nav__label"},n))}},6883:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(8042),i="tag_ZI38",l="tagRegular_xLtK",c="tagWithCount_bmIe";function s(e){var t=e.permalink,n=e.label,s=e.count;return r.createElement(o.Z,{href:t,className:(0,a.Z)(i,s?c:l)},n,s&&r.createElement("span",null,s))}},4101:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(4559),i=n(6883),l="tags_zp1X",c="tag_zi62";function s(e){var t=e.tags;return r.createElement(r.Fragment,null,r.createElement("b",null,r.createElement(o.Z,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list"},"Tags:")),r.createElement("ul",{className:(0,a.Z)(l,"padding--none","margin-left--sm")},t.map((function(e){var t=e.label,n=e.permalink;return r.createElement("li",{key:n,className:c},r.createElement(i.Z,{label:t,permalink:n}))}))))}},8485:function(e,t){function n(e){let t,n=[];for(let r of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(r))n.push(parseInt(r,10));else if(t=r.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,r,a,o]=t;if(r&&o){r=parseInt(r),o=parseInt(o);const e=r This is a React pageMy React page
\n
126){if(d>=55296&&d<=56319&&h
1)for(var n=1;n Your Docusaurus site did not load properly. A very common reason is a wrong site baseUrl configuration. Current configured baseUrl = '+e+" "+("/"===e?" (default value)":"")+' We suggest trying baseUrl = “万物之始,大道至简” 本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。 编写单元测试的基本方法其实很简单: 而一个好的单元测试的要求也很简单: 一个最简单的例子: 我们编写单元测试的步骤如下: 是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。 这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。 所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。 足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种: 编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。 我们依然看一个简单的例子: 如何覆盖?主动发送这个事件: 再看一个例子: 如何覆盖依赖组件的特定事件?主动触发依赖组件的事件: 足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种: 在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。 我们继续看一个简单的例子: 如何断言?我们可以断言副作用的影响结果: 有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了: 再看一个 React 组件的例子: 如何断言?判断依赖组件的变化: 始终记得要断言测试对象运行后对外界的副作用影响。 另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子: 更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。 适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。 编写良好的单元测试总结下来就是三条: 希望以上内容对大家有所帮助。/g,(function(){return n})).replace(/+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/.comment
can become .namespace--comment
) or replace them with your defined ones (like .editor__comment
). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the highlightAll
and highlightAllUnder
methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},9910:function(e,t,n){const r=n(5610),a=n(3908),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(557).resolve(t)],delete Prism.languages[e],n(557)(t),o.add(e)}))}i.silent=!1,e.exports=i},557:function(e,t,n){var r={"./":9910};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=557},3908:function(e){"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n基本方法
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});1
, 2
add(...)
expect(...).toBe(3)
输入
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});输出
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");重构与拆分
总结
blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
]]> +This is a test post.
A whole bunch of other information.
]]>Welcome to this blog. This blog is created with Docusaurus 2 alpha.
This is a test post.
A whole bunch of other information.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
]]>blog
directory. It supports tags as well!Delete the whole directory if you don't want the blog features. As simple as that!
]]>This is a test post.
A whole bunch of other information.
]]>Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
A população portuguesa é composta por 16,4 % com idade compreendida entre os 0 e os 14 anos, 66,2 % entre os 15 e os 64 anos e 17,4 % com mais de 65 anos, como tal, a população tem vindo a envelhecer. A esperança média de vida é de 78,04 anos. Em termos de alfabetização, 93,3 % sabem ler e escrever, tendo a taxa de analfabetismo vindo a descer ao longo dos anos.[82] O crescimento populacional situa-se nos 0,305 %, nascendo 10,45 por cada mil habitantes e falecendo 10,62 por cada mil habitantes, o que faz com que a população esteja a ser renovada, contribuindo para este facto a taxa de fertilidade que se situa nos 1,32 em 2010.[83] Portugal é um dos países com mais baixa taxa de mortalidade infantil abaixo dos 5 anos (3,7 por mil em 2010) no mundo.[84] quilométricas"
This is a link to another document. This is a link to an external page.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
You can write content using GitHub-flavored Markdown syntax.
To serve as an example page when styling markdown based Docusaurus sites.
Emphasis, aka italics, with asterisks or underscores.
Strong emphasis, aka bold, with asterisks or underscores.
Combined emphasis with asterisks and underscores.
Strikethrough uses two tildes. Scratch this.
I'm an inline-style link with title
You can use numbers for reference-style link definitions
Or leave it empty and use the link text itself.
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com/ or http://www.example.com/ and sometimes example.com (but not on GitHub, for example).
Some text to show that the reference links can follow later.
Here's our logo (hover to see the title text):
Inline-style:
Reference-style:
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
var s = "JavaScript syntax highlighting";
alert(s);
s = "Python syntax highlighting"
print(s)
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
function highlightMe() {
console.log("This line can be highlighted!");
}
Colons can be used to align columns.
Tables | Are | Cool |
---|---|---|
col 3 is | right-aligned | \$1600 |
col 2 is | centered | \$12 |
zebra stripes | are neat | \$1 |
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
Markdown | Less | Pretty |
---|---|---|
Still | renders | nicely |
1 | 2 | 3 |
Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
Quote break.
This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can put Markdown into a blockquote.
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a separate paragraph.
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the same paragraph.
This is a note
This is a tip
This is important
This is a caution
This is a warning
You can write JSX and use React components within your Markdown thanks to MDX.
Docusaurus green and Facebook blue are my favorite colors.I can write Markdown alongside my JSX!
5 minutes to learn the most important Docusaurus concepts.
Add Markdown or React files to src/pages to create a standalone page:
Documents are groups of pages connected through:
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Docusaurus supports Markdown and a few additional features.
Docusaurus is a static-site-generator (also called Jamstack).
You have just learned the basics of Docusaurus and made some changes to the initial template.
Let's discover Docusaurus in less than 5 minutes.
Get started by creating a new site.
Or try Docusaurus immediately with docusaurus.new.
Generate a new Docusaurus site using the classic template.
The classic template will automatically be added to your project after you run the command:
npm init docusaurus@latest my-website classic
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
Run the development server:
cd my-website
npm run start
The cd
command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The npm run start
command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open docs/intro.md
(this page) and edit some lines: the site reloads automatically and displays your changes.
This is a React page
You have just learned the basics of Docusaurus and made some changes to the initial template.
Docusaurus has much more to offer!
Have 5 more minutes? Take a look at versioning and i18n.
Anything unclear or buggy in this tutorial? Please report it!
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Create a file at blog/2021-02-28-greetings.md
:
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much you like.
A new blog post is now available at http://localhost:3000/blog/greetings
.
Documents are groups of pages connected through:
Create a markdown file at docs/hello.md
:
# Hello
This is my **first Docusaurus document**!
A new document is now available at http://localhost:3000/docs/hello
.
Docusaurus automatically creates a sidebar from the docs
folder.
Add metadata to customize the sidebar label and position:
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in sidebars.js
:
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
};
Add Markdown or React files to src/pages
to create a standalone page:
src/pages/index.js
-> localhost:3000/
src/pages/foo.md
-> localhost:3000/foo
src/pages/foo/bar.js
-> localhost:3000/foo/bar
Create a file at src/pages/my-react-page.js
:
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
A new page is now available at http://localhost:3000/my-react-page
.
Create a file at src/pages/my-markdown-page.md
:
# My Markdown page
This is a Markdown page
A new page is now available at http://localhost:3000/my-markdown-page
.
Docusaurus is a static-site-generator (also called Jamstack).
It builds your site as simple static HTML, JavaScript and CSS files.
Build your site for production:
npm run build
The static files are generated in the build
folder.
Test your production build locally:
npm run serve
The build
folder is now served at http://localhost:3000/
.
You can now deploy the build
folder almost anywhere easily, for free or very small cost (read the Deployment Guide).
Docusaurus supports Markdown and a few additional features.
Markdown documents have metadata at the top called Front Matter:
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
## Markdown heading
Markdown text with [links](./hello.md)
Regular Markdown links are supported, using url paths or relative file paths.
Let's see how to [Create a page](/create-a-page).
Let's see how to [Create a page](./create-a-page.md).
Result: Let's see how to Create a page.
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (static/img/docusaurus.png
):
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in the extra guides.
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
Use this awesome feature option
This action is dangerous
MDX can make your documentation more interactive and allows using any React components inside Markdown:
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is Docusaurus green !
This is Facebook blue !
Docusaurus can manage multiple versions of your docs.
Release a version 1.0 of your project:
npm run docusaurus docs:version 1.0
The docs
folder is copied into versioned_docs/version-1.0
and versions.json
is created.
Your docs now have 2 versions:
1.0
at http://localhost:3000/docs/
for the version 1.0 docscurrent
at http://localhost:3000/docs/next/
for the upcoming, unreleased docsTo navigate seamlessly across versions, add a version dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'docsVersionDropdown',
},
],
},
},
};
The docs version dropdown appears in your navbar:
It is possible to edit versioned docs in their respective folder:
versioned_docs/version-1.0/hello.md
updates http://localhost:3000/docs/hello
docs/hello.md
updates http://localhost:3000/docs/next/hello
Let's translate docs/intro.md
to French.
Modify docusaurus.config.js
to add support for the fr
locale:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
Copy the docs/intro.md
file to the i18n/fr
folder:
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
Translate i18n/fr/docusaurus-plugin-content-docs/current/intro.md
in French.
Start your site on the French locale:
npm run start -- --locale fr
Your localized site is accessible at http://localhost:3000/fr/
and the Getting Started
page is translated.
In development, you can only use one locale at a same time.
To navigate seamlessly across languages, add a locale dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
},
],
},
},
};
The locale dropdown now appears in your navbar:
Build your site for a specific locale:
npm run build -- --locale fr
Or build your site to include all the locales at once:
npm run build
An offline/local search example using @easyops-cn/docusaurus-search-local.
A Result You Can Trust.
Dozens of languages supported, including 中文分词 🇨🇳.
Looks pretty good, actually just like the Algolia Search on Docusaurus v2 website.
我们找不到您要找的页面。
请联系原始链接来源网站的所有者,并告知他们链接已损坏。
0&&(l=i.getRangeAt(0)),r.append(a),a.select(),a.selectionStart=0,a.selectionEnd=e.length;var c=!1;try{c=document.execCommand("copy")}catch(s){}a.remove(),l&&(i.removeAllRanges(),i.addRange(l)),o&&o.focus()}(t),i(!0),l.current=window.setTimeout((function(){i(!1)}),1e3)}),[t]);return(0,r.useEffect)((function(){return function(){return window.clearTimeout(l.current)}}),[]),r.createElement("button",{type:"button","aria-label":o?(0,K.I)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,K.I)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,K.I)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,u.Z)("clean-btn",n,U.copyButton,o&&U.copyButtonCopied),onClick:c},r.createElement("span",{className:U.copyButtonIcons,"aria-hidden":"true"},r.createElement("svg",{className:U.copyButtonIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})),r.createElement("svg",{className:U.copyButtonSuccessIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"}))))}var X="wordWrapButtonIcon_tV8z",$="wordWrapButtonEnabled_SVjJ";function J(e){var t=e.className,n=e.onClick,a=e.isEnabled,o=(0,K.I)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return r.createElement("button",{type:"button",onClick:n,className:(0,u.Z)("clean-btn",t,a&&$),"aria-label":o,title:o},r.createElement("svg",{className:X,viewBox:"0 0 24 24","aria-hidden":"true"},r.createElement("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})))}function Y(e){var t,n,a,i,l,c,s,m,f,v,h,g=e.children,b=e.className,E=void 0===b?"":b,k=e.metastring,C=e.title,Z=e.showLineNumbers,j=e.language,B=(0,d.L)().prism,O=B.defaultLanguage,L=B.magicComments,S=null!=(t=null!=j?j:null==(n=E.split(" ").find((function(e){return e.startsWith("language-")})))?void 0:n.replace(/language-/,""))?t:O,z=p(),P=(a=(0,r.useState)(!1),i=a[0],l=a[1],c=(0,r.useState)(!1),s=c[0],m=c[1],f=(0,r.useRef)(null),v=(0,r.useCallback)((function(){var e=f.current.querySelector("code");i?e.removeAttribute("style"):(e.style.whiteSpace="pre-wrap",e.style.overflowWrap="anywhere"),l((function(e){return!e}))}),[f,i]),h=(0,r.useCallback)((function(){var e=f.current,t=e.scrollWidth>e.clientWidth||f.current.querySelector("code").hasAttribute("style");m(t)}),[f]),_(f,h),(0,r.useEffect)((function(){h()}),[i,h]),(0,r.useEffect)((function(){return window.addEventListener("resize",h,{passive:!0}),function(){window.removeEventListener("resize",h)}}),[h]),{codeBlockRef:f,isEnabled:i,isCodeScrollable:s,toggle:v}),I=function(e){var t,n;return null!=(t=null==e||null==(n=e.match(y))?void 0:n.groups.title)?t:""}(k)||C,A=N(g,{metastring:k,language:S,magicComments:L}),D=A.lineClassNames,M=A.code,H=null!=Z?Z:function(e){return Boolean(null==e?void 0:e.includes("showLineNumbers"))}(k);return r.createElement(w,{as:"div",className:(0,u.Z)(E,S&&!E.includes("language-"+S)&&"language-"+S)},I&&r.createElement("div",{className:T.codeBlockTitle},I),r.createElement("div",{className:T.codeBlockContent},r.createElement(R,(0,o.Z)({},x,{theme:z,code:M,language:null!=S?S:"text"}),(function(e){var t=e.className,n=e.tokens,a=e.getLineProps,o=e.getTokenProps;return r.createElement("pre",{tabIndex:0,ref:P.codeBlockRef,className:(0,u.Z)(t,T.codeBlock,"thin-scrollbar")},r.createElement("code",{className:(0,u.Z)(T.codeBlockLines,H&&T.codeBlockLinesWithNumbering)},n.map((function(e,t){return r.createElement(F,{key:t,line:e,getLineProps:a,getTokenProps:o,classNames:D[t],showLineNumbers:H})}))))})),r.createElement("div",{className:T.buttonGroup},(P.isEnabled||P.isCodeScrollable)&&r.createElement(J,{className:T.codeButton,onClick:function(){return P.toggle()},isEnabled:P.isEnabled}),r.createElement(G,{className:T.codeButton,code:M}))))}var Q=["children"];function ee(e){var t=e.children,n=(0,i.Z)(e,Q),a=(0,s.Z)(),l=function(e){return r.Children.toArray(e).some((function(e){return(0,r.isValidElement)(e)}))?e:Array.isArray(e)?e.join(""):e}(t),c="string"==typeof l?Y:j;return r.createElement(c,(0,o.Z)({key:String(a)},n),l)}var te=n(8042);var ne=n(8594),re="details_XubN",ae="isBrowser_Zzsw",oe="collapsibleContent_NJ8j",ie=["summary","children"];function le(e){return!!e&&("SUMMARY"===e.tagName||le(e.parentElement))}function ce(e,t){return!!e&&(e===t||ce(e.parentElement,t))}function se(e){var t=e.summary,n=e.children,a=(0,i.Z)(e,ie),l=(0,s.Z)(),c=(0,r.useRef)(null),m=(0,ne.u)({initialState:!a.open}),d=m.collapsed,p=m.setCollapsed,f=(0,r.useState)(a.open),v=f[0],h=f[1];return r.createElement("details",(0,o.Z)({},a,{ref:c,open:v,"data-collapsed":d,className:(0,u.Z)(re,l&&ae,a.className),onMouseDown:function(e){le(e.target)&&e.detail>1&&e.preventDefault()},onClick:function(e){e.stopPropagation();var t=e.target;le(t)&&ce(t,c.current)&&(e.preventDefault(),d?(p(!1),h(!0)):p(!0))}}),null!=t?t:r.createElement("summary",null,"Details"),r.createElement(ne.z,{lazy:!1,collapsed:d,disableSSRStyle:!0,onCollapseTransitionEnd:function(e){p(e),h(!e)}},r.createElement("div",{className:oe},n)))}var ue="details_djZf";function me(e){var t=Object.assign({},e);return r.createElement(se,(0,o.Z)({},t,{className:(0,u.Z)("alert alert--info",ue,t.className)}))}var de=n(3668);function pe(e){return r.createElement(de.Z,e)}var fe="containsTaskList_PKEN";var ve="img_evYn";var he="admonition_DEdc",ge="admonitionHeading_UWCI",ye="admonitionIcon_fk7I",be="admonitionContent_Up4K";var Ee={note:{infimaClassName:"secondary",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"}))},label:r.createElement(K.Z,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)"},"note")},tip:{infimaClassName:"success",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)"},"tip")},danger:{infimaClassName:"danger",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"}))},label:r.createElement(K.Z,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)"},"danger")},info:{infimaClassName:"info",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)"},"info")},caution:{infimaClassName:"warning",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 16 16"},r.createElement("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"}))},label:r.createElement(K.Z,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)"},"caution")}},ke={secondary:"note",important:"info",success:"tip",warning:"danger"};function Ne(e){var t,n=function(e){var t=r.Children.toArray(e),n=t.find((function(e){var t;return r.isValidElement(e)&&"mdxAdmonitionTitle"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return{mdxAdmonitionTitle:n,rest:a}}(e.children),a=n.mdxAdmonitionTitle,o=n.rest;return Object.assign({},e,{title:null!=(t=e.title)?t:a,children:o})}var Ce={head:function(e){var t=r.Children.map(e.children,(function(e){return r.isValidElement(e)?function(e){var t;if(null!=(t=e.props)&&t.mdxType&&e.props.originalType){var n=e.props,a=(n.mdxType,n.originalType,(0,i.Z)(n,c));return r.createElement(e.props.originalType,a)}return e}(e):e}));return r.createElement(l.Z,e,t)},code:function(e){var t=["a","abbr","b","br","button","cite","code","del","dfn","em","i","img","input","ins","kbd","label","object","output","q","ruby","s","small","span","strong","sub","sup","time","u","var","wbr"];return r.Children.toArray(e.children).every((function(e){var n;return"string"==typeof e&&!e.includes("\n")||(0,r.isValidElement)(e)&&t.includes(null==(n=e.props)?void 0:n.mdxType)}))?r.createElement("code",e):r.createElement(ee,e)},a:function(e){return r.createElement(te.Z,e)},pre:function(e){var t;return r.createElement(ee,(0,r.isValidElement)(e.children)&&"code"===(null==(t=e.children.props)?void 0:t.originalType)?e.children.props:Object.assign({},e))},details:function(e){var t=r.Children.toArray(e.children),n=t.find((function(e){var t;return r.isValidElement(e)&&"summary"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return r.createElement(me,(0,o.Z)({},e,{summary:n}),a)},ul:function(e){return r.createElement("ul",(0,o.Z)({},e,{className:(t=e.className,(0,u.Z)(t,(null==t?void 0:t.includes("contains-task-list"))&&fe))}));var t},img:function(e){return r.createElement("img",(0,o.Z)({loading:"lazy"},e,{className:(t=e.className,(0,u.Z)(t,ve))}));var t},h1:function(e){return r.createElement(pe,(0,o.Z)({as:"h1"},e))},h2:function(e){return r.createElement(pe,(0,o.Z)({as:"h2"},e))},h3:function(e){return r.createElement(pe,(0,o.Z)({as:"h3"},e))},h4:function(e){return r.createElement(pe,(0,o.Z)({as:"h4"},e))},h5:function(e){return r.createElement(pe,(0,o.Z)({as:"h5"},e))},h6:function(e){return r.createElement(pe,(0,o.Z)({as:"h6"},e))},admonition:function(e){var t=Ne(e),n=t.children,a=t.type,o=t.title,i=t.icon,l=function(e){var t,n=null!=(t=ke[e])?t:e;return Ee[n]||(console.warn('No admonition config found for admonition type "'+n+'". Using Info as fallback.'),Ee.info)}(a),c=null!=o?o:l.label,s=l.iconComponent,m=null!=i?i:r.createElement(s,null);return r.createElement("div",{className:(0,u.Z)(f.k.common.admonition,f.k.common.admonitionType(e.type),"alert","alert--"+l.infimaClassName,he)},r.createElement("div",{className:ge},r.createElement("span",{className:ye},m),c),r.createElement("div",{className:be},n))},mermaid:function(){return null}};function Ze(e){var t=e.children;return r.createElement(a.Zo,{components:Ce},t)}},4621:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(3289),a=n(8795),o=n(8042);function i(e){var t=e.permalink,n=e.title,i=e.subLabel,l=e.isNext;return r.createElement(o.Z,{className:(0,a.Z)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t},i&&r.createElement("div",{className:"pagination-nav__sublabel"},i),r.createElement("div",{className:"pagination-nav__label"},n))}},6883:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(8042),i="tag_ZI38",l="tagRegular_xLtK",c="tagWithCount_bmIe";function s(e){var t=e.permalink,n=e.label,s=e.count;return r.createElement(o.Z,{href:t,className:(0,a.Z)(i,s?c:l)},n,s&&r.createElement("span",null,s))}},4101:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(4559),i=n(6883),l="tags_zp1X",c="tag_zi62";function s(e){var t=e.tags;return r.createElement(r.Fragment,null,r.createElement("b",null,r.createElement(o.Z,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list"},"Tags:")),r.createElement("ul",{className:(0,a.Z)(l,"padding--none","margin-left--sm")},t.map((function(e){var t=e.label,n=e.permalink;return r.createElement("li",{key:n,className:c},r.createElement(i.Z,{label:t,permalink:n}))}))))}},8485:function(e,t){function n(e){let t,n=[];for(let r of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(r))n.push(parseInt(r,10));else if(t=r.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,r,a,o]=t;if(r&&o){r=parseInt(r),o=parseInt(o);const e=r This is a React pageMy React page
\n
126){if(d>=55296&&d<=56319&&h
1)for(var n=1;n Your Docusaurus site did not load properly. A very common reason is a wrong site baseUrl configuration. Current configured baseUrl = '+e+" "+("/"===e?" (default value)":"")+' We suggest trying baseUrl = “万物之始,大道至简” 本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。 编写单元测试的基本方法其实很简单: 而一个好的单元测试的要求也很简单: 一个最简单的例子: 我们编写单元测试的步骤如下: 是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。 这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。 所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。 足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种: 编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。 我们依然看一个简单的例子: 如何覆盖?主动发送这个事件: 再看一个例子: 如何覆盖依赖组件的特定事件?主动触发依赖组件的事件: 足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种: 在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。 我们继续看一个简单的例子: 如何断言?我们可以断言副作用的影响结果: 有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了: 再看一个 React 组件的例子: 如何断言?判断依赖组件的变化: 始终记得要断言测试对象运行后对外界的副作用影响。 另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子: 更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。 适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。 编写良好的单元测试总结下来就是三条: 希望以上内容对大家有所帮助。0&&r.createElement(rn,{links:n}),logo:a&&r.createElement(sn,{logo:a}),copyright:t&&r.createElement(un,{copyright:t})})}var fn=r.memo(dn),pn=n(7271),hn="docusaurus.tab.",mn=r.createContext(void 0);var gn=(0,F.Qc)([$.S,k.pl,function(e){var t=e.children,n=function(){var e=(0,r.useState)({}),t=e[0],n=e[1],a=(0,r.useCallback)((function(e,t){(0,pn.W)("docusaurus.tab."+e).set(t)}),[]);(0,r.useEffect)((function(){try{var e={};(0,pn._)().forEach((function(t){if(t.startsWith(hn)){var n=t.substring(hn.length);e[n]=(0,pn.W)(t).get()}})),n(e)}catch(t){console.error(t)}}),[]);var o=(0,r.useCallback)((function(e,t){n((function(n){var r;return Object.assign({},n,((r={})[e]=t,r))})),a(e,t)}),[a]);return(0,r.useMemo)((function(){return{tabGroupChoices:t,setTabGroupChoices:o}}),[t,o])}();return r.createElement(mn.Provider,{value:n},t)},I.OC,Oe.L5,m.VC,function(e){var t=e.children;return r.createElement(M.n2,null,r.createElement(O.M,null,r.createElement(j,null,t)))}]);function vn(e){var t=e.children;return r.createElement(gn,null,t)}function bn(e){var t=e.error,n=e.tryAgain;return r.createElement("main",{className:"container margin-vert--xl"},r.createElement("div",{className:"row"},r.createElement("div",{className:"col col--6 col--offset-3"},r.createElement("h1",{className:"hero__title"},r.createElement(s.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("p",null,t.message),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},r.createElement(s.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again when the page crashed"},"Try again"))))))}var yn="mainWrapper_CgCW";function wn(e){var t=e.children,n=e.noFooter,i=e.wrapperClassName,l=e.title,s=e.description;return(0,v.t)(),r.createElement(vn,null,r.createElement(m.d,{title:l,description:s}),r.createElement(y,null),r.createElement(P,null),r.createElement(Vt,null),r.createElement("div",{id:c,className:(0,a.Z)(g.k.wrapper.main,yn,i)},r.createElement(o.Z,{fallback:function(e){return r.createElement(bn,e)}},t)),!n&&r.createElement(fn,null))}},541:function(e,t,n){"use strict";n.d(t,{Z:function(){return p}});var r=n(744),a=n(4690),o=n(3289),i=n(8042),l=n(4395),s=n(1778),u=n(4793),c=n(7518),d=["imageClassName","titleClassName"];function f(e){var t=e.logo,n=e.alt,r=e.imageClassName,a={light:(0,l.Z)(t.src),dark:(0,l.Z)(t.srcDark||t.src)},i=o.createElement(c.Z,{className:t.className,sources:a,height:t.height,width:t.width,alt:n,style:t.style});return r?o.createElement("div",{className:r},i):i}function p(e){var t,n=(0,s.Z)().siteConfig.title,c=(0,u.L)().navbar,p=c.title,h=c.logo,m=e.imageClassName,g=e.titleClassName,v=(0,a.Z)(e,d),b=(0,l.Z)((null==h?void 0:h.href)||"/"),y=p?"":n,w=null!=(t=null==h?void 0:h.alt)?t:y;return o.createElement(i.Z,(0,r.Z)({to:b},v,(null==h?void 0:h.target)&&{target:h.target}),h&&o.createElement(f,{logo:h,alt:w,imageClassName:m}),null!=p&&o.createElement("b",{className:g},p))}},8839:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(3289),a=n(2357);function o(e){var t=e.locale,n=e.version,o=e.tag,i=t;return r.createElement(a.Z,null,t&&r.createElement("meta",{name:"docusaurus_locale",content:t}),n&&r.createElement("meta",{name:"docusaurus_version",content:n}),o&&r.createElement("meta",{name:"docusaurus_tag",content:o}),i&&r.createElement("meta",{name:"docsearch:language",content:i}),n&&r.createElement("meta",{name:"docsearch:version",content:n}),o&&r.createElement("meta",{name:"docsearch:docusaurus_tag",content:o}))}},7518:function(e,t,n){"use strict";n.d(t,{Z:function(){return d}});var r=n(744),a=n(4690),o=n(3289),i=n(8795),l=n(508),s=n(1482),u={themedImage:"themedImage_wkt8","themedImage--light":"themedImage--light_MpfG","themedImage--dark":"themedImage--dark_nNh6"},c=["sources","className","alt"];function d(e){var t=(0,l.Z)(),n=(0,s.I)().colorMode,d=e.sources,f=e.className,p=e.alt,h=(0,a.Z)(e,c),m=t?"dark"===n?["dark"]:["light"]:["light","dark"];return o.createElement(o.Fragment,null,m.map((function(e){return o.createElement("img",(0,r.Z)({key:e,src:d[e],alt:p,className:(0,i.Z)(u.themedImage,u["themedImage--"+e],f)},h))})))}},8594:function(e,t,n){"use strict";n.d(t,{u:function(){return u},z:function(){return v}});var r=n(744),a=n(4690),o=n(3289),i=n(605),l=["collapsed"],s=["lazy"];function u(e){var t=e.initialState,n=(0,o.useState)(null!=t&&t),r=n[0],a=n[1],i=(0,o.useCallback)((function(){a((function(e){return!e}))}),[]);return{collapsed:r,setCollapsed:a,toggleCollapsed:i}}var c={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function f(e,t){var n=t?c:d;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function p(e){var t=e.collapsibleRef,n=e.collapsed,r=e.animation,a=(0,o.useRef)(!1);(0,o.useEffect)((function(){var e,o=t.current;function i(){var e,t,n=o.scrollHeight,a=null!=(e=null==r?void 0:r.duration)?e:function(e){var t=e/36;return Math.round(10*(4+15*Math.pow(t,.25)+t/5))}(n);return{transition:"height "+a+"ms "+(null!=(t=null==r?void 0:r.easing)?t:"ease-in-out"),height:n+"px"}}function l(){var e=i();o.style.transition=e.transition,o.style.height=e.height}if(!a.current)return f(o,n),void(a.current=!0);return o.style.willChange="height",e=requestAnimationFrame((function(){n?(l(),requestAnimationFrame((function(){o.style.height=c.height,o.style.overflow=c.overflow}))):(o.style.display="block",requestAnimationFrame((function(){l()})))})),function(){return cancelAnimationFrame(e)}}),[t,n,r])}function h(e){if(!i.Z.canUseDOM)return e?c:d}function m(e){var t=e.as,n=void 0===t?"div":t,r=e.collapsed,a=e.children,i=e.animation,l=e.onCollapseTransitionEnd,s=e.className,u=e.disableSSRStyle,c=(0,o.useRef)(null);return p({collapsibleRef:c,collapsed:r,animation:i}),o.createElement(n,{ref:c,style:u?void 0:h(r),onTransitionEnd:function(e){"height"===e.propertyName&&(f(c.current,r),null==l||l(r))},className:s},a)}function g(e){var t=e.collapsed,n=(0,a.Z)(e,l),i=(0,o.useState)(!t),s=i[0],u=i[1],c=(0,o.useState)(t),d=c[0],f=c[1];return(0,o.useLayoutEffect)((function(){t||u(!0)}),[t]),(0,o.useLayoutEffect)((function(){s&&f(t)}),[s,t]),s?o.createElement(m,(0,r.Z)({},n,{collapsed:d})):null}function v(e){var t=e.lazy,n=(0,a.Z)(e,s),r=t?g:m;return o.createElement(r,n)}},5462:function(e,t,n){"use strict";n.d(t,{nT:function(){return h},pl:function(){return p}});var r=n(3289),a=n(508),o=n(7271),i=n(2762),l=n(4793),s=(0,o.W)("docusaurus.announcement.dismiss"),u=(0,o.W)("docusaurus.announcement.id"),c=function(){return"true"===s.get()},d=function(e){return s.set(String(e))},f=r.createContext(null);function p(e){var t=e.children,n=function(){var e=(0,l.L)().announcementBar,t=(0,a.Z)(),n=(0,r.useState)((function(){return!!t&&c()})),o=n[0],i=n[1];(0,r.useEffect)((function(){i(c())}),[]);var s=(0,r.useCallback)((function(){d(!0),i(!0)}),[]);return(0,r.useEffect)((function(){if(e){var t=e.id,n=u.get();"annoucement-bar"===n&&(n="announcement-bar");var r=t!==n;u.set(t),r&&d(!1),!r&&c()||i(!1)}}),[e]),(0,r.useMemo)((function(){return{isActive:!!e&&!o,close:s}}),[e,o,s])}();return r.createElement(f.Provider,{value:n},t)}function h(){var e=(0,r.useContext)(f);if(!e)throw new i.i6("AnnouncementBarProvider");return e}},1482:function(e,t,n){"use strict";n.d(t,{I:function(){return g},S:function(){return m}});var r=n(3289),a=n(605),o=n(2762),i=n(7271),l=n(4793),s=r.createContext(void 0),u="theme",c=(0,i.W)(u),d="light",f="dark",p=function(e){return e===f?f:d};function h(){var e=(0,l.L)().colorMode,t=e.defaultMode,n=e.disableSwitch,o=e.respectPrefersColorScheme,i=(0,r.useState)(function(e){return a.Z.canUseDOM?p(document.documentElement.getAttribute("data-theme")):p(e)}(t)),s=i[0],h=i[1];(0,r.useEffect)((function(){n&&c.del()}),[n]);var m=(0,r.useCallback)((function(e,n){void 0===n&&(n={});var r=n.persist,a=void 0===r||r;e?(h(e),a&&function(e){c.set(p(e))}(e)):(h(o?window.matchMedia("(prefers-color-scheme: dark)").matches?f:d:t),c.del())}),[o,t]);(0,r.useEffect)((function(){document.documentElement.setAttribute("data-theme",p(s))}),[s]),(0,r.useEffect)((function(){if(!n){var e=function(e){if(e.key===u){var t=c.get();null!==t&&m(p(t))}};return window.addEventListener("storage",e),function(){return window.removeEventListener("storage",e)}}}),[n,m]);var g=(0,r.useRef)(!1);return(0,r.useEffect)((function(){if(!n||o){var e=window.matchMedia("(prefers-color-scheme: dark)"),t=function(){window.matchMedia("print").matches||g.current?g.current=window.matchMedia("print").matches:m(null)};return e.addListener(t),function(){return e.removeListener(t)}}}),[m,n,o]),(0,r.useMemo)((function(){return{colorMode:s,setColorMode:m,get isDarkTheme(){return s===f},setLightTheme:function(){m(d)},setDarkTheme:function(){m(f)}}}),[s,m])}function m(e){var t=e.children,n=h();return r.createElement(s.Provider,{value:n},t)}function g(){var e=(0,r.useContext)(s);if(null==e)throw new o.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},6159:function(e,t,n){"use strict";n.d(t,{J:function(){return y},L5:function(){return v}});var r=n(3289),a=n(4043),o=n(2732),i=n(4793),l=n(2036),s=n(2762),u=n(7271),c=function(e){return"docs-preferred-version-"+e},d=function(e,t,n){(0,u.W)(c(e),{persistence:t}).set(n)},f=function(e,t){return(0,u.W)(c(e),{persistence:t}).get()},p=function(e,t){(0,u.W)(c(e),{persistence:t}).del()};var h=r.createContext(null);function m(){var e=(0,a._r)(),t=(0,i.L)().docs.versionPersistence,n=(0,r.useMemo)((function(){return Object.keys(e)}),[e]),o=(0,r.useState)((function(){return function(e){return Object.fromEntries(e.map((function(e){return[e,{preferredVersionName:null}]})))}(n)})),l=o[0],s=o[1];return(0,r.useEffect)((function(){s(function(e){var t=e.pluginIds,n=e.versionPersistence,r=e.allDocsData;return Object.fromEntries(t.map((function(e){return[e,(t=e,a=f(t,n),r[t].versions.some((function(e){return e.name===a}))?{preferredVersionName:a}:(p(t,n),{preferredVersionName:null}))];var t,a})))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]),[l,(0,r.useMemo)((function(){return{savePreferredVersion:function(e,n){d(e,t,n),s((function(t){var r;return Object.assign({},t,((r={})[e]={preferredVersionName:n},r))}))}}}),[t])]}function g(e){var t=e.children,n=m();return r.createElement(h.Provider,{value:n},t)}function v(e){var t=e.children;return l.cE?r.createElement(g,null,t):r.createElement(r.Fragment,null,t)}function b(){var e=(0,r.useContext)(h);if(!e)throw new s.i6("DocsPreferredVersionContextProvider");return e}function y(e){var t;void 0===e&&(e=o.m);var n=(0,a.zh)(e),i=b(),l=i[0],s=i[1],u=l[e].preferredVersionName;return{preferredVersion:null!=(t=n.versions.find((function(e){return e.name===u})))?t:null,savePreferredVersionName:(0,r.useCallback)((function(t){s.savePreferredVersion(e,t)}),[s,e])}}},1877:function(e,t,n){"use strict";n.d(t,{V:function(){return s},b:function(){return l}});var r=n(3289),a=n(2762),o=Symbol("EmptyContext"),i=r.createContext(o);function l(e){var t=e.children,n=e.name,a=e.items,o=(0,r.useMemo)((function(){return n&&a?{name:n,items:a}:null}),[n,a]);return r.createElement(i.Provider,{value:o},t)}function s(){var e=(0,r.useContext)(i);if(e===o)throw new a.i6("DocsSidebarProvider");return e}},5278:function(e,t,n){"use strict";n.d(t,{E:function(){return l},q:function(){return i}});var r=n(3289),a=n(2762),o=r.createContext(null);function i(e){var t=e.children,n=e.version;return r.createElement(o.Provider,{value:n},t)}function l(){var e=(0,r.useContext)(o);if(null===e)throw new a.i6("DocsVersionProvider");return e}},2579:function(e,t,n){"use strict";n.d(t,{M:function(){return f},e:function(){return p}});var r=n(3289),a=n(5069),o=n(5668),i=n(4989),l=n(2762);function s(e){!function(e){var t=(0,i.k6)(),n=(0,l.zX)(e);(0,r.useEffect)((function(){return t.block((function(e,t){return n(e,t)}))}),[t,n])}((function(t,n){if("POP"===n)return e(t,n)}))}var u=n(4793),c=r.createContext(void 0);function d(){var e,t=(e=(0,a.HY)(),0===(0,u.L)().navbar.items.length&&!e.component),n=(0,o.i)(),i=!t&&"mobile"===n,l=(0,r.useState)(!1),c=l[0],d=l[1];s((function(){if(c)return d(!1),!1}));var f=(0,r.useCallback)((function(){d((function(e){return!e}))}),[]);return(0,r.useEffect)((function(){"desktop"===n&&d(!1)}),[n]),(0,r.useMemo)((function(){return{disabled:t,shouldRender:i,toggle:f,shown:c}}),[t,i,f,c])}function f(e){var t=e.children,n=d();return r.createElement(c.Provider,{value:n},t)}function p(){var e=r.useContext(c);if(void 0===e)throw new l.i6("NavbarMobileSidebarProvider");return e}},5069:function(e,t,n){"use strict";n.d(t,{HY:function(){return l},Zo:function(){return s},n2:function(){return i}});var r=n(3289),a=n(2762),o=r.createContext(null);function i(e){var t=e.children,n=(0,r.useState)({component:null,props:null});return r.createElement(o.Provider,{value:n},t)}function l(){var e=(0,r.useContext)(o);if(!e)throw new a.i6("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){var t=e.component,n=e.props,i=(0,r.useContext)(o);if(!i)throw new a.i6("NavbarSecondaryMenuContentProvider");var l=i[1],s=(0,a.Ql)(n);return(0,r.useEffect)((function(){l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((function(){return function(){return l({component:null,props:null})}}),[l]),null}},7886:function(e,t,n){"use strict";n.d(t,{h:function(){return a},t:function(){return o}});var r=n(3289),a="navigation-with-keyboard";function o(){(0,r.useEffect)((function(){function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),function(){document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},5668:function(e,t,n){"use strict";n.d(t,{i:function(){return u}});var r=n(3289),a=n(605),o="desktop",i="mobile",l="ssr";function s(){return a.Z.canUseDOM?window.innerWidth>996?o:i:l}function u(){var e=(0,r.useState)((function(){return s()})),t=e[0],n=e[1];return(0,r.useEffect)((function(){function e(){n(s())}return window.addEventListener("resize",e),function(){window.removeEventListener("resize",e),clearTimeout(undefined)}}),[]),t}},721:function(e,t,n){"use strict";n.d(t,{k:function(){return r}});var r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",admonitionType:function(e){return"theme-admonition-"+e}},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:function(e){return"theme-doc-sidebar-item-category-level-"+e},docSidebarItemLinkLevel:function(e){return"theme-doc-sidebar-item-link-level-"+e}},blog:{}}},2036:function(e,t,n){"use strict";n.d(t,{MN:function(){return C},Wl:function(){return m},_F:function(){return b},cE:function(){return p},jA:function(){return g},xz:function(){return h},hI:function(){return x},lO:function(){return k},vY:function(){return S},oz:function(){return E},s1:function(){return w}});var r=n(6952),a=n(3289),o=n(4989),i=n(4838),l=n(4043),s=n(6159),u=n(5278),c=n(1877);function d(e){return Array.from(new Set(e))}var f=n(272),p=!!l._r;function h(e){var t=(0,u.E)();if(e){var n=t.docs[e];if(!n)throw new Error("no version doc found by id="+e);return n}}function m(e){if(e.href)return e.href;for(var t,n=(0,r.Z)(e.items);!(t=n()).done;){var a=t.value;if("link"===a.type)return a.href;if("category"===a.type){var o=m(a);if(o)return o}}}function g(){var e=(0,o.TH)().pathname,t=(0,c.V)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");var n=y({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw new Error(e+" is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.");return n}var v=function(e,t){return void 0!==e&&(0,f.Mg)(e,t)};function b(e,t){return"link"===e.type?v(e.href,t):"category"===e.type&&(v(e.href,t)||function(e,t){return e.some((function(e){return b(e,t)}))}(e.items,t))}function y(e){var t=e.sidebarItems,n=e.pathname,a=e.onlyCategories,o=void 0!==a&&a,i=[];return function e(t){for(var a,l=(0,r.Z)(t);!(a=l()).done;){var s=a.value;if("category"===s.type&&((0,f.Mg)(s.href,n)||e(s.items))||"link"===s.type&&(0,f.Mg)(s.href,n))return o&&"category"!==s.type||i.unshift(s),!0}return!1}(t),i}function w(){var e,t=(0,c.V)(),n=(0,o.TH)().pathname;return!1!==(null==(e=(0,l.gA)())?void 0:e.pluginData.breadcrumbs)&&t?y({sidebarItems:t.items,pathname:n}):null}function k(e){var t=(0,l.Iw)(e).activeVersion,n=(0,s.J)(e).preferredVersion,r=(0,l.yW)(e);return(0,a.useMemo)((function(){return d([t,n,r].filter(Boolean))}),[t,n,r])}function E(e,t){var n=k(t);return(0,a.useMemo)((function(){var t=n.flatMap((function(e){return e.sidebars?Object.entries(e.sidebars):[]})),r=t.find((function(t){return t[0]===e}));if(!r)throw new Error("Can't find any sidebar with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((function(e){return e.name})).join(", ")+'".\n Available sidebar ids are:\n - '+Object.keys(t).join("\n- "));return r[1]}),[e,n])}function S(e,t){var n=k(t);return(0,a.useMemo)((function(){var t=n.flatMap((function(e){return e.docs})),r=t.find((function(t){return t.id===e}));if(!r){if(n.flatMap((function(e){return e.draftIds})).includes(e))return null;throw new Error("DocNavbarItem: couldn't find any doc with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((function(e){return e.name})).join(", ")+'".\nAvailable doc ids are:\n- '+d(t.map((function(e){return e.id}))).join("\n- "))}return r}),[e,n])}function x(e){var t=e.route,n=e.versionMetadata,r=(0,o.TH)(),a=t.routes,l=a.find((function(e){return(0,o.LX)(r.pathname,e)}));if(!l)return null;var s=l.sidebar,u=s?n.docsSidebars[s]:void 0;return{docElement:(0,i.H)(a),sidebarName:s,sidebarItems:u}}function C(e){return e.filter((function(e){return"category"!==e.type||!!m(e)}))}},4543:function(e,t,n){"use strict";n.d(t,{FG:function(){return f},d:function(){return c},VC:function(){return p}});var r=n(3289),a=n(8795),o=n(2357),i=n(8390);function l(){var e=r.useContext(i._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(4395),u=n(1778);function c(e){var t=e.title,n=e.description,a=e.keywords,i=e.image,l=e.children,c=function(e){var t=(0,u.Z)().siteConfig,n=t.title,r=t.titleDelimiter;return null!=e&&e.trim().length?e.trim()+" "+r+" "+n:n}(t),d=(0,s.C)().withBaseUrl,f=i?d(i,{absolute:!0}):void 0;return r.createElement(o.Z,null,t&&r.createElement("title",null,c),t&&r.createElement("meta",{property:"og:title",content:c}),n&&r.createElement("meta",{name:"description",content:n}),n&&r.createElement("meta",{property:"og:description",content:n}),a&&r.createElement("meta",{name:"keywords",content:Array.isArray(a)?a.join(","):a}),f&&r.createElement("meta",{property:"og:image",content:f}),f&&r.createElement("meta",{name:"twitter:image",content:f}),l)}var d=r.createContext(void 0);function f(e){var t=e.className,n=e.children,i=r.useContext(d),l=(0,a.Z)(i,t);return r.createElement(d.Provider,{value:l},r.createElement(o.Z,null,r.createElement("html",{className:l})),n)}function p(e){var t=e.children,n=l(),o="plugin-"+n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,""),i="plugin-id-"+n.plugin.id;return r.createElement(f,{className:(0,a.Z)(o,i)},t)}},2762:function(e,t,n){"use strict";n.d(t,{i6:function(){return h},Qc:function(){return g},zX:function(){return f},D9:function(){return p},Ql:function(){return m}});var r=n(9440),a=n(7367);function o(e){return o=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},o(e)}var i=n(7037);function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function s(e,t,n){return s=l()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var a=new(Function.bind.apply(e,r));return n&&(0,i.Z)(a,n.prototype),a},s.apply(null,arguments)}function u(e){var t="function"==typeof Map?new Map:void 0;return u=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return s(e,arguments,o(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),(0,i.Z)(r,e)},u(e)}var c=n(3289),d=n(605).Z.canUseDOM?c.useLayoutEffect:c.useEffect;function f(e){var t=(0,c.useRef)(e);return d((function(){t.current=e}),[e]),(0,c.useCallback)((function(){return t.current.apply(t,arguments)}),[])}function p(e){var t=(0,c.useRef)();return d((function(){t.current=e})),t.current}var h=function(e){function t(t,n){var a,o,i,l,s;return(s=e.call(this)||this).name="ReactContextError",s.message="Hook "+(null!=(a=null==(o=s.stack)||null==(i=o.split("\n")[1])||null==(l=i.match((0,r.Z)(/at (?:\w+\.)?(\w+)/,{name:1})))?void 0:l.groups.name)?a:"")+" is called outside the <"+t+">. "+(null!=n?n:""),s}return(0,a.Z)(t,e),t}(u(Error));function m(e){var t=Object.entries(e);return t.sort((function(e,t){return e[0].localeCompare(t[0])})),(0,c.useMemo)((function(){return e}),t.flat())}function g(e){return function(t){var n=t.children;return c.createElement(c.Fragment,null,e.reduceRight((function(e,t){return c.createElement(t,null,e)}),n))}}},272:function(e,t,n){"use strict";n.d(t,{Mg:function(){return i},Ns:function(){return l}});var r=n(3289),a=n(5119),o=n(1778);function i(e,t){var n=function(e){var t;return null==(t=!e||e.endsWith("/")?e:e+"/")?void 0:t.toLowerCase()};return n(e)===n(t)}function l(){var e=(0,o.Z)().siteConfig.baseUrl;return(0,r.useMemo)((function(){return function(e){var t=e.baseUrl;function n(e){return e.path===t&&!0===e.exact}function r(e){return e.path===t&&!e.exact}return function e(t){if(0!==t.length)return t.find(n)||e(t.filter(r).flatMap((function(e){var t;return null!=(t=e.routes)?t:[]})))}(e.routes)}({routes:a.Z,baseUrl:e})}),[e])}},4351:function(e,t,n){"use strict";n.d(t,{Ct:function(){return f},OC:function(){return s},RF:function(){return d}});var r=n(3289),a=n(605),o=n(508),i=n(2762);var l=r.createContext(void 0);function s(e){var t,n=e.children,a=(t=(0,r.useRef)(!0),(0,r.useMemo)((function(){return{scrollEventsEnabledRef:t,enableScrollEvents:function(){t.current=!0},disableScrollEvents:function(){t.current=!1}}}),[]));return r.createElement(l.Provider,{value:a},n)}function u(){var e=(0,r.useContext)(l);if(null==e)throw new i.i6("ScrollControllerProvider");return e}var c=function(){return a.Z.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null};function d(e,t){void 0===t&&(t=[]);var n=u().scrollEventsEnabledRef,a=(0,r.useRef)(c()),o=(0,i.zX)(e);(0,r.useEffect)((function(){var e=function(){if(n.current){var e=c();o(e,a.current),a.current=e}},t={passive:!0};return e(),window.addEventListener("scroll",e,t),function(){return window.removeEventListener("scroll",e,t)}}),[o,n].concat(t))}function f(){var e=(0,r.useRef)(null),t=(0,o.Z)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:function(n){e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),function(){}}(n):function(e){var t=null,n=document.documentElement.scrollTop>e;return function r(){var a=document.documentElement.scrollTop;(n&&a>e||!n&&a/g,(function(){return n})).replace(/+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/0||a==r||u)break;u=!0}}for(;;){if(l>=(p=t[r]).s_size){if(this.cursor=o+p.s_size,!p.method)return p.result;var m=p.method();if(this.cursor=o+p.s_size,m)return p.result}if((r=p.substring_i)<0)return 0}},find_among_b:function(t,n){for(var r=0,a=n,o=this.cursor,i=this.limit_backward,l=0,s=0,u=!1;;){for(var c=r+(a-r>>1),d=0,f=l=0;p--){if(o-f==i){d=-1;break}if(d=e.charCodeAt(o-1-f)-h.s[p])break;f++}if(d<0?(a=c,s=f):(r=c,l=f),a-r<=1){if(r>0||a==r||u)break;u=!0}}for(;;){var h;if(l>=(h=t[r]).s_size){if(this.cursor=o-h.s_size,!h.method)return h.result;var m=h.method();if(this.cursor=o-h.s_size,m)return h.result}if((r=h.substring_i)<0)return 0}},replace_s:function(t,n,r){var a=r.length-(n-t),o=e.substring(0,t),i=e.substring(n);return e=o+r+i,this.limit+=a,this.cursor>=n?this.cursor+=a:this.cursor>t&&(this.cursor=t),a},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>e.length)throw"faulty slice operation"},slice_from:function(e){this.slice_check(),this.replace_s(this.bra,this.ket,e)},slice_del:function(){this.slice_from("")},insert:function(e,t,n){var r=this.replace_s(e,t,n);e<=this.bra&&(this.bra+=r),e<=this.ket&&(this.ket+=r)},slice_to:function(){return this.slice_check(),e.substring(this.bra,this.ket)},eq_v_b:function(e){return this.eq_s_b(e.length,e)}}}},e.trimmerSupport={generateTrimmer:function(e){var t=new RegExp("^[^"+e+"]+"),n=new RegExp("[^"+e+"]+$");return function(e){return"function"==typeof e.update?e.update((function(e){return e.replace(t,"").replace(n,"")})):e.replace(t,"").replace(n,"")}}}}})?r.call(t,n,t,e):r)||(e.exports=a)},6512:function(e,t,n){var r,a;!function(){var o,i,l,s,u,c,d,f,p,h,m,g,v,b,y,w,k,E,S,x,C,_,T,N,L,A,D=function(e){var t=new D.Builder;return t.pipeline.add(D.trimmer,D.stopWordFilter,D.stemmer),t.searchPipeline.add(D.stemmer),e.call(t,t),t.build()};D.version="2.3.9",D.utils={},D.utils.warn=(o=this,function(e){o.console&&console.warn&&console.warn(e)}),D.utils.asString=function(e){return null==e?"":e.toString()},D.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),n=Object.keys(e),r=0;r.comment
can become .namespace--comment
) or replace them with your defined ones (like .editor__comment
). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the highlightAll
and highlightAllUnder
methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},9910:function(e,t,n){const r=n(5610),a=n(3908),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(557).resolve(t)],delete Prism.languages[e],n(557)(t),o.add(e)}))}i.silent=!1,e.exports=i},557:function(e,t,n){var r={"./":9910};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=557},3908:function(e){"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n基本方法
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});1
, 2
add(...)
expect(...).toBe(3)
输入
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});输出
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");重构与拆分
总结
blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
]]> +This is a test post.
A whole bunch of other information.
]]>Welcome to this blog. This blog is created with Docusaurus 2 alpha.
This is a test post.
A whole bunch of other information.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
]]>blog
directory. It supports tags as well!Delete the whole directory if you don't want the blog features. As simple as that!
]]>This is a test post.
A whole bunch of other information.
]]>Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
A população portuguesa é composta por 16,4 % com idade compreendida entre os 0 e os 14 anos, 66,2 % entre os 15 e os 64 anos e 17,4 % com mais de 65 anos, como tal, a população tem vindo a envelhecer. A esperança média de vida é de 78,04 anos. Em termos de alfabetização, 93,3 % sabem ler e escrever, tendo a taxa de analfabetismo vindo a descer ao longo dos anos.[82] O crescimento populacional situa-se nos 0,305 %, nascendo 10,45 por cada mil habitantes e falecendo 10,62 por cada mil habitantes, o que faz com que a população esteja a ser renovada, contribuindo para este facto a taxa de fertilidade que se situa nos 1,32 em 2010.[83] Portugal é um dos países com mais baixa taxa de mortalidade infantil abaixo dos 5 anos (3,7 por mil em 2010) no mundo.[84] quilométricas"
This is a link to another document. This is a link to an external page.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
You can write content using GitHub-flavored Markdown syntax.
To serve as an example page when styling markdown based Docusaurus sites.
Emphasis, aka italics, with asterisks or underscores.
Strong emphasis, aka bold, with asterisks or underscores.
Combined emphasis with asterisks and underscores.
Strikethrough uses two tildes. Scratch this.
I'm an inline-style link with title
You can use numbers for reference-style link definitions
Or leave it empty and use the link text itself.
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com/ or http://www.example.com/ and sometimes example.com (but not on GitHub, for example).
Some text to show that the reference links can follow later.
Here's our logo (hover to see the title text):
Inline-style:
Reference-style:
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
var s = "JavaScript syntax highlighting";
alert(s);
s = "Python syntax highlighting"
print(s)
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
function highlightMe() {
console.log("This line can be highlighted!");
}
Colons can be used to align columns.
Tables | Are | Cool |
---|---|---|
col 3 is | right-aligned | \$1600 |
col 2 is | centered | \$12 |
zebra stripes | are neat | \$1 |
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
Markdown | Less | Pretty |
---|---|---|
Still | renders | nicely |
1 | 2 | 3 |
Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
Quote break.
This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can put Markdown into a blockquote.
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a separate paragraph.
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the same paragraph.
This is a note
This is a tip
This is important
This is a caution
This is a warning
You can write JSX and use React components within your Markdown thanks to MDX.
Docusaurus green and Facebook blue are my favorite colors.I can write Markdown alongside my JSX!
5 minutes to learn the most important Docusaurus concepts.
Add Markdown or React files to src/pages to create a standalone page:
Documents are groups of pages connected through:
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Docusaurus supports Markdown and a few additional features.
Docusaurus is a static-site-generator (also called Jamstack).
You have just learned the basics of Docusaurus and made some changes to the initial template.
Let's discover Docusaurus in less than 5 minutes.
Get started by creating a new site.
Or try Docusaurus immediately with docusaurus.new.
Generate a new Docusaurus site using the classic template.
The classic template will automatically be added to your project after you run the command:
npm init docusaurus@latest my-website classic
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
Run the development server:
cd my-website
npm run start
The cd
command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The npm run start
command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open docs/intro.md
(this page) and edit some lines: the site reloads automatically and displays your changes.
This is a React page
You have just learned the basics of Docusaurus and made some changes to the initial template.
Docusaurus has much more to offer!
Have 5 more minutes? Take a look at versioning and i18n.
Anything unclear or buggy in this tutorial? Please report it!
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Create a file at blog/2021-02-28-greetings.md
:
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much you like.
A new blog post is now available at http://localhost:3000/blog/greetings
.
Documents are groups of pages connected through:
Create a markdown file at docs/hello.md
:
# Hello
This is my **first Docusaurus document**!
A new document is now available at http://localhost:3000/docs/hello
.
Docusaurus automatically creates a sidebar from the docs
folder.
Add metadata to customize the sidebar label and position:
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in sidebars.js
:
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
};
Add Markdown or React files to src/pages
to create a standalone page:
src/pages/index.js
-> localhost:3000/
src/pages/foo.md
-> localhost:3000/foo
src/pages/foo/bar.js
-> localhost:3000/foo/bar
Create a file at src/pages/my-react-page.js
:
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
A new page is now available at http://localhost:3000/my-react-page
.
Create a file at src/pages/my-markdown-page.md
:
# My Markdown page
This is a Markdown page
A new page is now available at http://localhost:3000/my-markdown-page
.
Docusaurus is a static-site-generator (also called Jamstack).
It builds your site as simple static HTML, JavaScript and CSS files.
Build your site for production:
npm run build
The static files are generated in the build
folder.
Test your production build locally:
npm run serve
The build
folder is now served at http://localhost:3000/
.
You can now deploy the build
folder almost anywhere easily, for free or very small cost (read the Deployment Guide).
Docusaurus supports Markdown and a few additional features.
Markdown documents have metadata at the top called Front Matter:
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
## Markdown heading
Markdown text with [links](./hello.md)
Regular Markdown links are supported, using url paths or relative file paths.
Let's see how to [Create a page](/create-a-page).
Let's see how to [Create a page](./create-a-page.md).
Result: Let's see how to Create a page.
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (static/img/docusaurus.png
):
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in the extra guides.
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
Use this awesome feature option
This action is dangerous
MDX can make your documentation more interactive and allows using any React components inside Markdown:
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is Docusaurus green !
This is Facebook blue !
Docusaurus can manage multiple versions of your docs.
Release a version 1.0 of your project:
npm run docusaurus docs:version 1.0
The docs
folder is copied into versioned_docs/version-1.0
and versions.json
is created.
Your docs now have 2 versions:
1.0
at http://localhost:3000/docs/
for the version 1.0 docscurrent
at http://localhost:3000/docs/next/
for the upcoming, unreleased docsTo navigate seamlessly across versions, add a version dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'docsVersionDropdown',
},
],
},
},
};
The docs version dropdown appears in your navbar:
It is possible to edit versioned docs in their respective folder:
versioned_docs/version-1.0/hello.md
updates http://localhost:3000/docs/hello
docs/hello.md
updates http://localhost:3000/docs/next/hello
Let's translate docs/intro.md
to French.
Modify docusaurus.config.js
to add support for the fr
locale:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
Copy the docs/intro.md
file to the i18n/fr
folder:
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
Translate i18n/fr/docusaurus-plugin-content-docs/current/intro.md
in French.
Start your site on the French locale:
npm run start -- --locale fr
Your localized site is accessible at http://localhost:3000/fr/
and the Getting Started
page is translated.
In development, you can only use one locale at a same time.
To navigate seamlessly across languages, add a locale dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
},
],
},
},
};
The locale dropdown now appears in your navbar:
Build your site for a specific locale:
npm run build -- --locale fr
Or build your site to include all the locales at once:
npm run build
An offline/local search example using @easyops-cn/docusaurus-search-local.
A Result You Can Trust.
Dozens of languages supported, including 中文分词 🇨🇳.
Looks pretty good, actually just like the Algolia Search on Docusaurus v2 website.
我們沒有您要找的頁面。
請聯絡原始連結來源網站的所有者,並通知他們連結已毀損。
0&&(l=i.getRangeAt(0)),r.append(a),a.select(),a.selectionStart=0,a.selectionEnd=e.length;var c=!1;try{c=document.execCommand("copy")}catch(s){}a.remove(),l&&(i.removeAllRanges(),i.addRange(l)),o&&o.focus()}(t),i(!0),l.current=window.setTimeout((function(){i(!1)}),1e3)}),[t]);return(0,r.useEffect)((function(){return function(){return window.clearTimeout(l.current)}}),[]),r.createElement("button",{type:"button","aria-label":o?(0,K.I)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,K.I)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,K.I)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,u.Z)("clean-btn",n,U.copyButton,o&&U.copyButtonCopied),onClick:c},r.createElement("span",{className:U.copyButtonIcons,"aria-hidden":"true"},r.createElement("svg",{className:U.copyButtonIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})),r.createElement("svg",{className:U.copyButtonSuccessIcon,viewBox:"0 0 24 24"},r.createElement("path",{d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"}))))}var X="wordWrapButtonIcon_tV8z",$="wordWrapButtonEnabled_SVjJ";function J(e){var t=e.className,n=e.onClick,a=e.isEnabled,o=(0,K.I)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return r.createElement("button",{type:"button",onClick:n,className:(0,u.Z)("clean-btn",t,a&&$),"aria-label":o,title:o},r.createElement("svg",{className:X,viewBox:"0 0 24 24","aria-hidden":"true"},r.createElement("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})))}function Y(e){var t,n,a,i,l,c,s,m,f,v,h,g=e.children,b=e.className,E=void 0===b?"":b,k=e.metastring,C=e.title,Z=e.showLineNumbers,j=e.language,B=(0,d.L)().prism,O=B.defaultLanguage,L=B.magicComments,S=null!=(t=null!=j?j:null==(n=E.split(" ").find((function(e){return e.startsWith("language-")})))?void 0:n.replace(/language-/,""))?t:O,z=p(),P=(a=(0,r.useState)(!1),i=a[0],l=a[1],c=(0,r.useState)(!1),s=c[0],m=c[1],f=(0,r.useRef)(null),v=(0,r.useCallback)((function(){var e=f.current.querySelector("code");i?e.removeAttribute("style"):(e.style.whiteSpace="pre-wrap",e.style.overflowWrap="anywhere"),l((function(e){return!e}))}),[f,i]),h=(0,r.useCallback)((function(){var e=f.current,t=e.scrollWidth>e.clientWidth||f.current.querySelector("code").hasAttribute("style");m(t)}),[f]),_(f,h),(0,r.useEffect)((function(){h()}),[i,h]),(0,r.useEffect)((function(){return window.addEventListener("resize",h,{passive:!0}),function(){window.removeEventListener("resize",h)}}),[h]),{codeBlockRef:f,isEnabled:i,isCodeScrollable:s,toggle:v}),I=function(e){var t,n;return null!=(t=null==e||null==(n=e.match(y))?void 0:n.groups.title)?t:""}(k)||C,A=N(g,{metastring:k,language:S,magicComments:L}),D=A.lineClassNames,M=A.code,H=null!=Z?Z:function(e){return Boolean(null==e?void 0:e.includes("showLineNumbers"))}(k);return r.createElement(w,{as:"div",className:(0,u.Z)(E,S&&!E.includes("language-"+S)&&"language-"+S)},I&&r.createElement("div",{className:T.codeBlockTitle},I),r.createElement("div",{className:T.codeBlockContent},r.createElement(R,(0,o.Z)({},x,{theme:z,code:M,language:null!=S?S:"text"}),(function(e){var t=e.className,n=e.tokens,a=e.getLineProps,o=e.getTokenProps;return r.createElement("pre",{tabIndex:0,ref:P.codeBlockRef,className:(0,u.Z)(t,T.codeBlock,"thin-scrollbar")},r.createElement("code",{className:(0,u.Z)(T.codeBlockLines,H&&T.codeBlockLinesWithNumbering)},n.map((function(e,t){return r.createElement(F,{key:t,line:e,getLineProps:a,getTokenProps:o,classNames:D[t],showLineNumbers:H})}))))})),r.createElement("div",{className:T.buttonGroup},(P.isEnabled||P.isCodeScrollable)&&r.createElement(J,{className:T.codeButton,onClick:function(){return P.toggle()},isEnabled:P.isEnabled}),r.createElement(G,{className:T.codeButton,code:M}))))}var Q=["children"];function ee(e){var t=e.children,n=(0,i.Z)(e,Q),a=(0,s.Z)(),l=function(e){return r.Children.toArray(e).some((function(e){return(0,r.isValidElement)(e)}))?e:Array.isArray(e)?e.join(""):e}(t),c="string"==typeof l?Y:j;return r.createElement(c,(0,o.Z)({key:String(a)},n),l)}var te=n(8042);var ne=n(8594),re="details_XubN",ae="isBrowser_Zzsw",oe="collapsibleContent_NJ8j",ie=["summary","children"];function le(e){return!!e&&("SUMMARY"===e.tagName||le(e.parentElement))}function ce(e,t){return!!e&&(e===t||ce(e.parentElement,t))}function se(e){var t=e.summary,n=e.children,a=(0,i.Z)(e,ie),l=(0,s.Z)(),c=(0,r.useRef)(null),m=(0,ne.u)({initialState:!a.open}),d=m.collapsed,p=m.setCollapsed,f=(0,r.useState)(a.open),v=f[0],h=f[1];return r.createElement("details",(0,o.Z)({},a,{ref:c,open:v,"data-collapsed":d,className:(0,u.Z)(re,l&&ae,a.className),onMouseDown:function(e){le(e.target)&&e.detail>1&&e.preventDefault()},onClick:function(e){e.stopPropagation();var t=e.target;le(t)&&ce(t,c.current)&&(e.preventDefault(),d?(p(!1),h(!0)):p(!0))}}),null!=t?t:r.createElement("summary",null,"Details"),r.createElement(ne.z,{lazy:!1,collapsed:d,disableSSRStyle:!0,onCollapseTransitionEnd:function(e){p(e),h(!e)}},r.createElement("div",{className:oe},n)))}var ue="details_djZf";function me(e){var t=Object.assign({},e);return r.createElement(se,(0,o.Z)({},t,{className:(0,u.Z)("alert alert--info",ue,t.className)}))}var de=n(3668);function pe(e){return r.createElement(de.Z,e)}var fe="containsTaskList_PKEN";var ve="img_evYn";var he="admonition_DEdc",ge="admonitionHeading_UWCI",ye="admonitionIcon_fk7I",be="admonitionContent_Up4K";var Ee={note:{infimaClassName:"secondary",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"}))},label:r.createElement(K.Z,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)"},"note")},tip:{infimaClassName:"success",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)"},"tip")},danger:{infimaClassName:"danger",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 12 16"},r.createElement("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"}))},label:r.createElement(K.Z,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)"},"danger")},info:{infimaClassName:"info",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 14 16"},r.createElement("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"}))},label:r.createElement(K.Z,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)"},"info")},caution:{infimaClassName:"warning",iconComponent:function(){return r.createElement("svg",{viewBox:"0 0 16 16"},r.createElement("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"}))},label:r.createElement(K.Z,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)"},"caution")}},ke={secondary:"note",important:"info",success:"tip",warning:"danger"};function Ne(e){var t,n=function(e){var t=r.Children.toArray(e),n=t.find((function(e){var t;return r.isValidElement(e)&&"mdxAdmonitionTitle"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return{mdxAdmonitionTitle:n,rest:a}}(e.children),a=n.mdxAdmonitionTitle,o=n.rest;return Object.assign({},e,{title:null!=(t=e.title)?t:a,children:o})}var Ce={head:function(e){var t=r.Children.map(e.children,(function(e){return r.isValidElement(e)?function(e){var t;if(null!=(t=e.props)&&t.mdxType&&e.props.originalType){var n=e.props,a=(n.mdxType,n.originalType,(0,i.Z)(n,c));return r.createElement(e.props.originalType,a)}return e}(e):e}));return r.createElement(l.Z,e,t)},code:function(e){var t=["a","abbr","b","br","button","cite","code","del","dfn","em","i","img","input","ins","kbd","label","object","output","q","ruby","s","small","span","strong","sub","sup","time","u","var","wbr"];return r.Children.toArray(e.children).every((function(e){var n;return"string"==typeof e&&!e.includes("\n")||(0,r.isValidElement)(e)&&t.includes(null==(n=e.props)?void 0:n.mdxType)}))?r.createElement("code",e):r.createElement(ee,e)},a:function(e){return r.createElement(te.Z,e)},pre:function(e){var t;return r.createElement(ee,(0,r.isValidElement)(e.children)&&"code"===(null==(t=e.children.props)?void 0:t.originalType)?e.children.props:Object.assign({},e))},details:function(e){var t=r.Children.toArray(e.children),n=t.find((function(e){var t;return r.isValidElement(e)&&"summary"===(null==(t=e.props)?void 0:t.mdxType)})),a=r.createElement(r.Fragment,null,t.filter((function(e){return e!==n})));return r.createElement(me,(0,o.Z)({},e,{summary:n}),a)},ul:function(e){return r.createElement("ul",(0,o.Z)({},e,{className:(t=e.className,(0,u.Z)(t,(null==t?void 0:t.includes("contains-task-list"))&&fe))}));var t},img:function(e){return r.createElement("img",(0,o.Z)({loading:"lazy"},e,{className:(t=e.className,(0,u.Z)(t,ve))}));var t},h1:function(e){return r.createElement(pe,(0,o.Z)({as:"h1"},e))},h2:function(e){return r.createElement(pe,(0,o.Z)({as:"h2"},e))},h3:function(e){return r.createElement(pe,(0,o.Z)({as:"h3"},e))},h4:function(e){return r.createElement(pe,(0,o.Z)({as:"h4"},e))},h5:function(e){return r.createElement(pe,(0,o.Z)({as:"h5"},e))},h6:function(e){return r.createElement(pe,(0,o.Z)({as:"h6"},e))},admonition:function(e){var t=Ne(e),n=t.children,a=t.type,o=t.title,i=t.icon,l=function(e){var t,n=null!=(t=ke[e])?t:e;return Ee[n]||(console.warn('No admonition config found for admonition type "'+n+'". Using Info as fallback.'),Ee.info)}(a),c=null!=o?o:l.label,s=l.iconComponent,m=null!=i?i:r.createElement(s,null);return r.createElement("div",{className:(0,u.Z)(f.k.common.admonition,f.k.common.admonitionType(e.type),"alert","alert--"+l.infimaClassName,he)},r.createElement("div",{className:ge},r.createElement("span",{className:ye},m),c),r.createElement("div",{className:be},n))},mermaid:function(){return null}};function Ze(e){var t=e.children;return r.createElement(a.Zo,{components:Ce},t)}},4621:function(e,t,n){"use strict";n.d(t,{Z:function(){return i}});var r=n(3289),a=n(8795),o=n(8042);function i(e){var t=e.permalink,n=e.title,i=e.subLabel,l=e.isNext;return r.createElement(o.Z,{className:(0,a.Z)("pagination-nav__link",l?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t},i&&r.createElement("div",{className:"pagination-nav__sublabel"},i),r.createElement("div",{className:"pagination-nav__label"},n))}},6883:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(8042),i="tag_ZI38",l="tagRegular_xLtK",c="tagWithCount_bmIe";function s(e){var t=e.permalink,n=e.label,s=e.count;return r.createElement(o.Z,{href:t,className:(0,a.Z)(i,s?c:l)},n,s&&r.createElement("span",null,s))}},4101:function(e,t,n){"use strict";n.d(t,{Z:function(){return s}});var r=n(3289),a=n(8795),o=n(4559),i=n(6883),l="tags_zp1X",c="tag_zi62";function s(e){var t=e.tags;return r.createElement(r.Fragment,null,r.createElement("b",null,r.createElement(o.Z,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list"},"Tags:")),r.createElement("ul",{className:(0,a.Z)(l,"padding--none","margin-left--sm")},t.map((function(e){var t=e.label,n=e.permalink;return r.createElement("li",{key:n,className:c},r.createElement(i.Z,{label:t,permalink:n}))}))))}},8485:function(e,t){function n(e){let t,n=[];for(let r of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(r))n.push(parseInt(r,10));else if(t=r.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,r,a,o]=t;if(r&&o){r=parseInt(r),o=parseInt(o);const e=r This is a React pageMy React page
\n
126){if(d>=55296&&d<=56319&&h
1)for(var n=1;n Your Docusaurus site did not load properly. A very common reason is a wrong site baseUrl configuration. Current configured baseUrl = '+e+" "+("/"===e?" (default value)":"")+' We suggest trying baseUrl = “万物之始,大道至简” 本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。 编写单元测试的基本方法其实很简单: 而一个好的单元测试的要求也很简单: 一个最简单的例子: 我们编写单元测试的步骤如下: 是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。 这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。 所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。 足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种: 编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。 我们依然看一个简单的例子: 如何覆盖?主动发送这个事件: 再看一个例子: 如何覆盖依赖组件的特定事件?主动触发依赖组件的事件: 足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种: 在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。 我们继续看一个简单的例子: 如何断言?我们可以断言副作用的影响结果: 有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了: 再看一个 React 组件的例子: 如何断言?判断依赖组件的变化: 始终记得要断言测试对象运行后对外界的副作用影响。 另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子: 更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。 适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。 编写良好的单元测试总结下来就是三条: 希望以上内容对大家有所帮助。0&&r.createElement(rn,{links:n}),logo:a&&r.createElement(sn,{logo:a}),copyright:t&&r.createElement(un,{copyright:t})})}var fn=r.memo(dn),pn=n(7271),hn="docusaurus.tab.",mn=r.createContext(void 0);var gn=(0,F.Qc)([Z.S,k.pl,function(e){var t=e.children,n=function(){var e=(0,r.useState)({}),t=e[0],n=e[1],a=(0,r.useCallback)((function(e,t){(0,pn.W)("docusaurus.tab."+e).set(t)}),[]);(0,r.useEffect)((function(){try{var e={};(0,pn._)().forEach((function(t){if(t.startsWith(hn)){var n=t.substring(hn.length);e[n]=(0,pn.W)(t).get()}})),n(e)}catch(t){console.error(t)}}),[]);var o=(0,r.useCallback)((function(e,t){n((function(n){var r;return Object.assign({},n,((r={})[e]=t,r))})),a(e,t)}),[a]);return(0,r.useMemo)((function(){return{tabGroupChoices:t,setTabGroupChoices:o}}),[t,o])}();return r.createElement(mn.Provider,{value:n},t)},I.OC,Oe.L5,m.VC,function(e){var t=e.children;return r.createElement(M.n2,null,r.createElement(O.M,null,r.createElement(j,null,t)))}]);function vn(e){var t=e.children;return r.createElement(gn,null,t)}function bn(e){var t=e.error,n=e.tryAgain;return r.createElement("main",{className:"container margin-vert--xl"},r.createElement("div",{className:"row"},r.createElement("div",{className:"col col--6 col--offset-3"},r.createElement("h1",{className:"hero__title"},r.createElement(s.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("p",null,t.message),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},r.createElement(s.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again when the page crashed"},"Try again"))))))}var yn="mainWrapper_CgCW";function wn(e){var t=e.children,n=e.noFooter,i=e.wrapperClassName,l=e.title,s=e.description;return(0,v.t)(),r.createElement(vn,null,r.createElement(m.d,{title:l,description:s}),r.createElement(y,null),r.createElement(N,null),r.createElement(Qt,null),r.createElement("div",{id:c,className:(0,a.Z)(g.k.wrapper.main,yn,i)},r.createElement(o.Z,{fallback:function(e){return r.createElement(bn,e)}},t)),!n&&r.createElement(fn,null))}},541:function(e,t,n){"use strict";n.d(t,{Z:function(){return p}});var r=n(744),a=n(4690),o=n(3289),i=n(8042),l=n(4395),s=n(1778),u=n(4793),c=n(7518),d=["imageClassName","titleClassName"];function f(e){var t=e.logo,n=e.alt,r=e.imageClassName,a={light:(0,l.Z)(t.src),dark:(0,l.Z)(t.srcDark||t.src)},i=o.createElement(c.Z,{className:t.className,sources:a,height:t.height,width:t.width,alt:n,style:t.style});return r?o.createElement("div",{className:r},i):i}function p(e){var t,n=(0,s.Z)().siteConfig.title,c=(0,u.L)().navbar,p=c.title,h=c.logo,m=e.imageClassName,g=e.titleClassName,v=(0,a.Z)(e,d),b=(0,l.Z)((null==h?void 0:h.href)||"/"),y=p?"":n,w=null!=(t=null==h?void 0:h.alt)?t:y;return o.createElement(i.Z,(0,r.Z)({to:b},v,(null==h?void 0:h.target)&&{target:h.target}),h&&o.createElement(f,{logo:h,alt:w,imageClassName:m}),null!=p&&o.createElement("b",{className:g},p))}},8839:function(e,t,n){"use strict";n.d(t,{Z:function(){return o}});var r=n(3289),a=n(2357);function o(e){var t=e.locale,n=e.version,o=e.tag,i=t;return r.createElement(a.Z,null,t&&r.createElement("meta",{name:"docusaurus_locale",content:t}),n&&r.createElement("meta",{name:"docusaurus_version",content:n}),o&&r.createElement("meta",{name:"docusaurus_tag",content:o}),i&&r.createElement("meta",{name:"docsearch:language",content:i}),n&&r.createElement("meta",{name:"docsearch:version",content:n}),o&&r.createElement("meta",{name:"docsearch:docusaurus_tag",content:o}))}},7518:function(e,t,n){"use strict";n.d(t,{Z:function(){return d}});var r=n(744),a=n(4690),o=n(3289),i=n(8795),l=n(508),s=n(1482),u={themedImage:"themedImage_wkt8","themedImage--light":"themedImage--light_MpfG","themedImage--dark":"themedImage--dark_nNh6"},c=["sources","className","alt"];function d(e){var t=(0,l.Z)(),n=(0,s.I)().colorMode,d=e.sources,f=e.className,p=e.alt,h=(0,a.Z)(e,c),m=t?"dark"===n?["dark"]:["light"]:["light","dark"];return o.createElement(o.Fragment,null,m.map((function(e){return o.createElement("img",(0,r.Z)({key:e,src:d[e],alt:p,className:(0,i.Z)(u.themedImage,u["themedImage--"+e],f)},h))})))}},8594:function(e,t,n){"use strict";n.d(t,{u:function(){return u},z:function(){return v}});var r=n(744),a=n(4690),o=n(3289),i=n(605),l=["collapsed"],s=["lazy"];function u(e){var t=e.initialState,n=(0,o.useState)(null!=t&&t),r=n[0],a=n[1],i=(0,o.useCallback)((function(){a((function(e){return!e}))}),[]);return{collapsed:r,setCollapsed:a,toggleCollapsed:i}}var c={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function f(e,t){var n=t?c:d;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function p(e){var t=e.collapsibleRef,n=e.collapsed,r=e.animation,a=(0,o.useRef)(!1);(0,o.useEffect)((function(){var e,o=t.current;function i(){var e,t,n=o.scrollHeight,a=null!=(e=null==r?void 0:r.duration)?e:function(e){var t=e/36;return Math.round(10*(4+15*Math.pow(t,.25)+t/5))}(n);return{transition:"height "+a+"ms "+(null!=(t=null==r?void 0:r.easing)?t:"ease-in-out"),height:n+"px"}}function l(){var e=i();o.style.transition=e.transition,o.style.height=e.height}if(!a.current)return f(o,n),void(a.current=!0);return o.style.willChange="height",e=requestAnimationFrame((function(){n?(l(),requestAnimationFrame((function(){o.style.height=c.height,o.style.overflow=c.overflow}))):(o.style.display="block",requestAnimationFrame((function(){l()})))})),function(){return cancelAnimationFrame(e)}}),[t,n,r])}function h(e){if(!i.Z.canUseDOM)return e?c:d}function m(e){var t=e.as,n=void 0===t?"div":t,r=e.collapsed,a=e.children,i=e.animation,l=e.onCollapseTransitionEnd,s=e.className,u=e.disableSSRStyle,c=(0,o.useRef)(null);return p({collapsibleRef:c,collapsed:r,animation:i}),o.createElement(n,{ref:c,style:u?void 0:h(r),onTransitionEnd:function(e){"height"===e.propertyName&&(f(c.current,r),null==l||l(r))},className:s},a)}function g(e){var t=e.collapsed,n=(0,a.Z)(e,l),i=(0,o.useState)(!t),s=i[0],u=i[1],c=(0,o.useState)(t),d=c[0],f=c[1];return(0,o.useLayoutEffect)((function(){t||u(!0)}),[t]),(0,o.useLayoutEffect)((function(){s&&f(t)}),[s,t]),s?o.createElement(m,(0,r.Z)({},n,{collapsed:d})):null}function v(e){var t=e.lazy,n=(0,a.Z)(e,s),r=t?g:m;return o.createElement(r,n)}},5462:function(e,t,n){"use strict";n.d(t,{nT:function(){return h},pl:function(){return p}});var r=n(3289),a=n(508),o=n(7271),i=n(2762),l=n(4793),s=(0,o.W)("docusaurus.announcement.dismiss"),u=(0,o.W)("docusaurus.announcement.id"),c=function(){return"true"===s.get()},d=function(e){return s.set(String(e))},f=r.createContext(null);function p(e){var t=e.children,n=function(){var e=(0,l.L)().announcementBar,t=(0,a.Z)(),n=(0,r.useState)((function(){return!!t&&c()})),o=n[0],i=n[1];(0,r.useEffect)((function(){i(c())}),[]);var s=(0,r.useCallback)((function(){d(!0),i(!0)}),[]);return(0,r.useEffect)((function(){if(e){var t=e.id,n=u.get();"annoucement-bar"===n&&(n="announcement-bar");var r=t!==n;u.set(t),r&&d(!1),!r&&c()||i(!1)}}),[e]),(0,r.useMemo)((function(){return{isActive:!!e&&!o,close:s}}),[e,o,s])}();return r.createElement(f.Provider,{value:n},t)}function h(){var e=(0,r.useContext)(f);if(!e)throw new i.i6("AnnouncementBarProvider");return e}},1482:function(e,t,n){"use strict";n.d(t,{I:function(){return g},S:function(){return m}});var r=n(3289),a=n(605),o=n(2762),i=n(7271),l=n(4793),s=r.createContext(void 0),u="theme",c=(0,i.W)(u),d="light",f="dark",p=function(e){return e===f?f:d};function h(){var e=(0,l.L)().colorMode,t=e.defaultMode,n=e.disableSwitch,o=e.respectPrefersColorScheme,i=(0,r.useState)(function(e){return a.Z.canUseDOM?p(document.documentElement.getAttribute("data-theme")):p(e)}(t)),s=i[0],h=i[1];(0,r.useEffect)((function(){n&&c.del()}),[n]);var m=(0,r.useCallback)((function(e,n){void 0===n&&(n={});var r=n.persist,a=void 0===r||r;e?(h(e),a&&function(e){c.set(p(e))}(e)):(h(o?window.matchMedia("(prefers-color-scheme: dark)").matches?f:d:t),c.del())}),[o,t]);(0,r.useEffect)((function(){document.documentElement.setAttribute("data-theme",p(s))}),[s]),(0,r.useEffect)((function(){if(!n){var e=function(e){if(e.key===u){var t=c.get();null!==t&&m(p(t))}};return window.addEventListener("storage",e),function(){return window.removeEventListener("storage",e)}}}),[n,m]);var g=(0,r.useRef)(!1);return(0,r.useEffect)((function(){if(!n||o){var e=window.matchMedia("(prefers-color-scheme: dark)"),t=function(){window.matchMedia("print").matches||g.current?g.current=window.matchMedia("print").matches:m(null)};return e.addListener(t),function(){return e.removeListener(t)}}}),[m,n,o]),(0,r.useMemo)((function(){return{colorMode:s,setColorMode:m,get isDarkTheme(){return s===f},setLightTheme:function(){m(d)},setDarkTheme:function(){m(f)}}}),[s,m])}function m(e){var t=e.children,n=h();return r.createElement(s.Provider,{value:n},t)}function g(){var e=(0,r.useContext)(s);if(null==e)throw new o.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},6159:function(e,t,n){"use strict";n.d(t,{J:function(){return y},L5:function(){return v}});var r=n(3289),a=n(4043),o=n(2732),i=n(4793),l=n(2036),s=n(2762),u=n(7271),c=function(e){return"docs-preferred-version-"+e},d=function(e,t,n){(0,u.W)(c(e),{persistence:t}).set(n)},f=function(e,t){return(0,u.W)(c(e),{persistence:t}).get()},p=function(e,t){(0,u.W)(c(e),{persistence:t}).del()};var h=r.createContext(null);function m(){var e=(0,a._r)(),t=(0,i.L)().docs.versionPersistence,n=(0,r.useMemo)((function(){return Object.keys(e)}),[e]),o=(0,r.useState)((function(){return function(e){return Object.fromEntries(e.map((function(e){return[e,{preferredVersionName:null}]})))}(n)})),l=o[0],s=o[1];return(0,r.useEffect)((function(){s(function(e){var t=e.pluginIds,n=e.versionPersistence,r=e.allDocsData;return Object.fromEntries(t.map((function(e){return[e,(t=e,a=f(t,n),r[t].versions.some((function(e){return e.name===a}))?{preferredVersionName:a}:(p(t,n),{preferredVersionName:null}))];var t,a})))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]),[l,(0,r.useMemo)((function(){return{savePreferredVersion:function(e,n){d(e,t,n),s((function(t){var r;return Object.assign({},t,((r={})[e]={preferredVersionName:n},r))}))}}}),[t])]}function g(e){var t=e.children,n=m();return r.createElement(h.Provider,{value:n},t)}function v(e){var t=e.children;return l.cE?r.createElement(g,null,t):r.createElement(r.Fragment,null,t)}function b(){var e=(0,r.useContext)(h);if(!e)throw new s.i6("DocsPreferredVersionContextProvider");return e}function y(e){var t;void 0===e&&(e=o.m);var n=(0,a.zh)(e),i=b(),l=i[0],s=i[1],u=l[e].preferredVersionName;return{preferredVersion:null!=(t=n.versions.find((function(e){return e.name===u})))?t:null,savePreferredVersionName:(0,r.useCallback)((function(t){s.savePreferredVersion(e,t)}),[s,e])}}},1877:function(e,t,n){"use strict";n.d(t,{V:function(){return s},b:function(){return l}});var r=n(3289),a=n(2762),o=Symbol("EmptyContext"),i=r.createContext(o);function l(e){var t=e.children,n=e.name,a=e.items,o=(0,r.useMemo)((function(){return n&&a?{name:n,items:a}:null}),[n,a]);return r.createElement(i.Provider,{value:o},t)}function s(){var e=(0,r.useContext)(i);if(e===o)throw new a.i6("DocsSidebarProvider");return e}},5278:function(e,t,n){"use strict";n.d(t,{E:function(){return l},q:function(){return i}});var r=n(3289),a=n(2762),o=r.createContext(null);function i(e){var t=e.children,n=e.version;return r.createElement(o.Provider,{value:n},t)}function l(){var e=(0,r.useContext)(o);if(null===e)throw new a.i6("DocsVersionProvider");return e}},2579:function(e,t,n){"use strict";n.d(t,{M:function(){return f},e:function(){return p}});var r=n(3289),a=n(5069),o=n(5668),i=n(4989),l=n(2762);function s(e){!function(e){var t=(0,i.k6)(),n=(0,l.zX)(e);(0,r.useEffect)((function(){return t.block((function(e,t){return n(e,t)}))}),[t,n])}((function(t,n){if("POP"===n)return e(t,n)}))}var u=n(4793),c=r.createContext(void 0);function d(){var e,t=(e=(0,a.HY)(),0===(0,u.L)().navbar.items.length&&!e.component),n=(0,o.i)(),i=!t&&"mobile"===n,l=(0,r.useState)(!1),c=l[0],d=l[1];s((function(){if(c)return d(!1),!1}));var f=(0,r.useCallback)((function(){d((function(e){return!e}))}),[]);return(0,r.useEffect)((function(){"desktop"===n&&d(!1)}),[n]),(0,r.useMemo)((function(){return{disabled:t,shouldRender:i,toggle:f,shown:c}}),[t,i,f,c])}function f(e){var t=e.children,n=d();return r.createElement(c.Provider,{value:n},t)}function p(){var e=r.useContext(c);if(void 0===e)throw new l.i6("NavbarMobileSidebarProvider");return e}},5069:function(e,t,n){"use strict";n.d(t,{HY:function(){return l},Zo:function(){return s},n2:function(){return i}});var r=n(3289),a=n(2762),o=r.createContext(null);function i(e){var t=e.children,n=(0,r.useState)({component:null,props:null});return r.createElement(o.Provider,{value:n},t)}function l(){var e=(0,r.useContext)(o);if(!e)throw new a.i6("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){var t=e.component,n=e.props,i=(0,r.useContext)(o);if(!i)throw new a.i6("NavbarSecondaryMenuContentProvider");var l=i[1],s=(0,a.Ql)(n);return(0,r.useEffect)((function(){l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((function(){return function(){return l({component:null,props:null})}}),[l]),null}},7886:function(e,t,n){"use strict";n.d(t,{h:function(){return a},t:function(){return o}});var r=n(3289),a="navigation-with-keyboard";function o(){(0,r.useEffect)((function(){function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),function(){document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},5668:function(e,t,n){"use strict";n.d(t,{i:function(){return u}});var r=n(3289),a=n(605),o="desktop",i="mobile",l="ssr";function s(){return a.Z.canUseDOM?window.innerWidth>996?o:i:l}function u(){var e=(0,r.useState)((function(){return s()})),t=e[0],n=e[1];return(0,r.useEffect)((function(){function e(){n(s())}return window.addEventListener("resize",e),function(){window.removeEventListener("resize",e),clearTimeout(undefined)}}),[]),t}},721:function(e,t,n){"use strict";n.d(t,{k:function(){return r}});var r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",admonitionType:function(e){return"theme-admonition-"+e}},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:function(e){return"theme-doc-sidebar-item-category-level-"+e},docSidebarItemLinkLevel:function(e){return"theme-doc-sidebar-item-link-level-"+e}},blog:{}}},2036:function(e,t,n){"use strict";n.d(t,{MN:function(){return T},Wl:function(){return m},_F:function(){return b},cE:function(){return p},jA:function(){return g},xz:function(){return h},hI:function(){return S},lO:function(){return k},vY:function(){return x},oz:function(){return E},s1:function(){return w}});var r=n(6952),a=n(3289),o=n(4989),i=n(4838),l=n(4043),s=n(6159),u=n(5278),c=n(1877);function d(e){return Array.from(new Set(e))}var f=n(272),p=!!l._r;function h(e){var t=(0,u.E)();if(e){var n=t.docs[e];if(!n)throw new Error("no version doc found by id="+e);return n}}function m(e){if(e.href)return e.href;for(var t,n=(0,r.Z)(e.items);!(t=n()).done;){var a=t.value;if("link"===a.type)return a.href;if("category"===a.type){var o=m(a);if(o)return o}}}function g(){var e=(0,o.TH)().pathname,t=(0,c.V)();if(!t)throw new Error("Unexpected: cant find current sidebar in context");var n=y({sidebarItems:t.items,pathname:e,onlyCategories:!0}).slice(-1)[0];if(!n)throw new Error(e+" is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.");return n}var v=function(e,t){return void 0!==e&&(0,f.Mg)(e,t)};function b(e,t){return"link"===e.type?v(e.href,t):"category"===e.type&&(v(e.href,t)||function(e,t){return e.some((function(e){return b(e,t)}))}(e.items,t))}function y(e){var t=e.sidebarItems,n=e.pathname,a=e.onlyCategories,o=void 0!==a&&a,i=[];return function e(t){for(var a,l=(0,r.Z)(t);!(a=l()).done;){var s=a.value;if("category"===s.type&&((0,f.Mg)(s.href,n)||e(s.items))||"link"===s.type&&(0,f.Mg)(s.href,n))return o&&"category"!==s.type||i.unshift(s),!0}return!1}(t),i}function w(){var e,t=(0,c.V)(),n=(0,o.TH)().pathname;return!1!==(null==(e=(0,l.gA)())?void 0:e.pluginData.breadcrumbs)&&t?y({sidebarItems:t.items,pathname:n}):null}function k(e){var t=(0,l.Iw)(e).activeVersion,n=(0,s.J)(e).preferredVersion,r=(0,l.yW)(e);return(0,a.useMemo)((function(){return d([t,n,r].filter(Boolean))}),[t,n,r])}function E(e,t){var n=k(t);return(0,a.useMemo)((function(){var t=n.flatMap((function(e){return e.sidebars?Object.entries(e.sidebars):[]})),r=t.find((function(t){return t[0]===e}));if(!r)throw new Error("Can't find any sidebar with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((function(e){return e.name})).join(", ")+'".\n Available sidebar ids are:\n - '+Object.keys(t).join("\n- "));return r[1]}),[e,n])}function x(e,t){var n=k(t);return(0,a.useMemo)((function(){var t=n.flatMap((function(e){return e.docs})),r=t.find((function(t){return t.id===e}));if(!r){if(n.flatMap((function(e){return e.draftIds})).includes(e))return null;throw new Error("DocNavbarItem: couldn't find any doc with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((function(e){return e.name})).join(", ")+'".\nAvailable doc ids are:\n- '+d(t.map((function(e){return e.id}))).join("\n- "))}return r}),[e,n])}function S(e){var t=e.route,n=e.versionMetadata,r=(0,o.TH)(),a=t.routes,l=a.find((function(e){return(0,o.LX)(r.pathname,e)}));if(!l)return null;var s=l.sidebar,u=s?n.docsSidebars[s]:void 0;return{docElement:(0,i.H)(a),sidebarName:s,sidebarItems:u}}function T(e){return e.filter((function(e){return"category"!==e.type||!!m(e)}))}},4543:function(e,t,n){"use strict";n.d(t,{FG:function(){return f},d:function(){return c},VC:function(){return p}});var r=n(3289),a=n(8795),o=n(2357),i=n(8390);function l(){var e=r.useContext(i._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(4395),u=n(1778);function c(e){var t=e.title,n=e.description,a=e.keywords,i=e.image,l=e.children,c=function(e){var t=(0,u.Z)().siteConfig,n=t.title,r=t.titleDelimiter;return null!=e&&e.trim().length?e.trim()+" "+r+" "+n:n}(t),d=(0,s.C)().withBaseUrl,f=i?d(i,{absolute:!0}):void 0;return r.createElement(o.Z,null,t&&r.createElement("title",null,c),t&&r.createElement("meta",{property:"og:title",content:c}),n&&r.createElement("meta",{name:"description",content:n}),n&&r.createElement("meta",{property:"og:description",content:n}),a&&r.createElement("meta",{name:"keywords",content:Array.isArray(a)?a.join(","):a}),f&&r.createElement("meta",{property:"og:image",content:f}),f&&r.createElement("meta",{name:"twitter:image",content:f}),l)}var d=r.createContext(void 0);function f(e){var t=e.className,n=e.children,i=r.useContext(d),l=(0,a.Z)(i,t);return r.createElement(d.Provider,{value:l},r.createElement(o.Z,null,r.createElement("html",{className:l})),n)}function p(e){var t=e.children,n=l(),o="plugin-"+n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,""),i="plugin-id-"+n.plugin.id;return r.createElement(f,{className:(0,a.Z)(o,i)},t)}},2762:function(e,t,n){"use strict";n.d(t,{i6:function(){return h},Qc:function(){return g},zX:function(){return f},D9:function(){return p},Ql:function(){return m}});var r=n(9440),a=n(7367);function o(e){return o=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},o(e)}var i=n(7037);function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function s(e,t,n){return s=l()?Reflect.construct.bind():function(e,t,n){var r=[null];r.push.apply(r,t);var a=new(Function.bind.apply(e,r));return n&&(0,i.Z)(a,n.prototype),a},s.apply(null,arguments)}function u(e){var t="function"==typeof Map?new Map:void 0;return u=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return s(e,arguments,o(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),(0,i.Z)(r,e)},u(e)}var c=n(3289),d=n(605).Z.canUseDOM?c.useLayoutEffect:c.useEffect;function f(e){var t=(0,c.useRef)(e);return d((function(){t.current=e}),[e]),(0,c.useCallback)((function(){return t.current.apply(t,arguments)}),[])}function p(e){var t=(0,c.useRef)();return d((function(){t.current=e})),t.current}var h=function(e){function t(t,n){var a,o,i,l,s;return(s=e.call(this)||this).name="ReactContextError",s.message="Hook "+(null!=(a=null==(o=s.stack)||null==(i=o.split("\n")[1])||null==(l=i.match((0,r.Z)(/at (?:\w+\.)?(\w+)/,{name:1})))?void 0:l.groups.name)?a:"")+" is called outside the <"+t+">. "+(null!=n?n:""),s}return(0,a.Z)(t,e),t}(u(Error));function m(e){var t=Object.entries(e);return t.sort((function(e,t){return e[0].localeCompare(t[0])})),(0,c.useMemo)((function(){return e}),t.flat())}function g(e){return function(t){var n=t.children;return c.createElement(c.Fragment,null,e.reduceRight((function(e,t){return c.createElement(t,null,e)}),n))}}},272:function(e,t,n){"use strict";n.d(t,{Mg:function(){return i},Ns:function(){return l}});var r=n(3289),a=n(5119),o=n(1778);function i(e,t){var n=function(e){var t;return null==(t=!e||e.endsWith("/")?e:e+"/")?void 0:t.toLowerCase()};return n(e)===n(t)}function l(){var e=(0,o.Z)().siteConfig.baseUrl;return(0,r.useMemo)((function(){return function(e){var t=e.baseUrl;function n(e){return e.path===t&&!0===e.exact}function r(e){return e.path===t&&!e.exact}return function e(t){if(0!==t.length)return t.find(n)||e(t.filter(r).flatMap((function(e){var t;return null!=(t=e.routes)?t:[]})))}(e.routes)}({routes:a.Z,baseUrl:e})}),[e])}},4351:function(e,t,n){"use strict";n.d(t,{Ct:function(){return f},OC:function(){return s},RF:function(){return d}});var r=n(3289),a=n(605),o=n(508),i=n(2762);var l=r.createContext(void 0);function s(e){var t,n=e.children,a=(t=(0,r.useRef)(!0),(0,r.useMemo)((function(){return{scrollEventsEnabledRef:t,enableScrollEvents:function(){t.current=!0},disableScrollEvents:function(){t.current=!1}}}),[]));return r.createElement(l.Provider,{value:a},n)}function u(){var e=(0,r.useContext)(l);if(null==e)throw new i.i6("ScrollControllerProvider");return e}var c=function(){return a.Z.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null};function d(e,t){void 0===t&&(t=[]);var n=u().scrollEventsEnabledRef,a=(0,r.useRef)(c()),o=(0,i.zX)(e);(0,r.useEffect)((function(){var e=function(){if(n.current){var e=c();o(e,a.current),a.current=e}},t={passive:!0};return e(),window.addEventListener("scroll",e,t),function(){return window.removeEventListener("scroll",e,t)}}),[o,n].concat(t))}function f(){var e=(0,r.useRef)(null),t=(0,o.Z)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:function(n){e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),function(){}}(n):function(e){var t=null,n=document.documentElement.scrollTop>e;return function r(){var a=document.documentElement.scrollTop;(n&&a>e||!n&&a/g,(function(){return n})).replace(/+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/0||a==r||u)break;u=!0}}for(;;){if(l>=(p=t[r]).s_size){if(this.cursor=o+p.s_size,!p.method)return p.result;var m=p.method();if(this.cursor=o+p.s_size,m)return p.result}if((r=p.substring_i)<0)return 0}},find_among_b:function(t,n){for(var r=0,a=n,o=this.cursor,i=this.limit_backward,l=0,s=0,u=!1;;){for(var c=r+(a-r>>1),d=0,f=l=0;p--){if(o-f==i){d=-1;break}if(d=e.charCodeAt(o-1-f)-h.s[p])break;f++}if(d<0?(a=c,s=f):(r=c,l=f),a-r<=1){if(r>0||a==r||u)break;u=!0}}for(;;){var h;if(l>=(h=t[r]).s_size){if(this.cursor=o-h.s_size,!h.method)return h.result;var m=h.method();if(this.cursor=o-h.s_size,m)return h.result}if((r=h.substring_i)<0)return 0}},replace_s:function(t,n,r){var a=r.length-(n-t),o=e.substring(0,t),i=e.substring(n);return e=o+r+i,this.limit+=a,this.cursor>=n?this.cursor+=a:this.cursor>t&&(this.cursor=t),a},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>e.length)throw"faulty slice operation"},slice_from:function(e){this.slice_check(),this.replace_s(this.bra,this.ket,e)},slice_del:function(){this.slice_from("")},insert:function(e,t,n){var r=this.replace_s(e,t,n);e<=this.bra&&(this.bra+=r),e<=this.ket&&(this.ket+=r)},slice_to:function(){return this.slice_check(),e.substring(this.bra,this.ket)},eq_v_b:function(e){return this.eq_s_b(e.length,e)}}}},e.trimmerSupport={generateTrimmer:function(e){var t=new RegExp("^[^"+e+"]+"),n=new RegExp("[^"+e+"]+$");return function(e){return"function"==typeof e.update?e.update((function(e){return e.replace(t,"").replace(n,"")})):e.replace(t,"").replace(n,"")}}}}})?r.call(t,n,t,e):r)||(e.exports=a)},6512:function(e,t,n){var r,a;!function(){var o,i,l,s,u,c,d,f,p,h,m,g,v,b,y,w,k,E,x,S,T,C,_,L,A,D,R=function(e){var t=new R.Builder;return t.pipeline.add(R.trimmer,R.stopWordFilter,R.stemmer),t.searchPipeline.add(R.stemmer),e.call(t,t),t.build()};R.version="2.3.9",R.utils={},R.utils.warn=(o=this,function(e){o.console&&console.warn&&console.warn(e)}),R.utils.asString=function(e){return null==e?"":e.toString()},R.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),n=Object.keys(e),r=0;r.comment
can become .namespace--comment
) or replace them with your defined ones (like .editor__comment
). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the highlightAll
and highlightAllUnder
methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},9910:function(e,t,n){const r=n(5610),a=n(3908),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(557).resolve(t)],delete Prism.languages[e],n(557)(t),o.add(e)}))}i.silent=!1,e.exports=i},557:function(e,t,n){var r={"./":9910};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=557},3908:function(e){"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n基本方法
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});1
, 2
add(...)
expect(...).toBe(3)
输入
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});输出
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");重构与拆分
总结
blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
]]> +This is a test post.
A whole bunch of other information.
]]>Welcome to this blog. This blog is created with Docusaurus 2 alpha.
This is a test post.
A whole bunch of other information.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
]]>blog
directory. It supports tags as well!Delete the whole directory if you don't want the blog features. As simple as that!
]]>This is a test post.
A whole bunch of other information.
]]>Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
Welcome to this blog. This blog is created with Docusaurus 2 alpha.
Blog features are powered by the blog plugin. Simply add files to the blog
directory. It supports tags as well!
Delete the whole directory if you don't want the blog features. As simple as that!
“万物之始,大道至简”
本文尝试从简单的单元测试思想着手,探讨如何编写良好的单元测试。以下将主要基于 TypeScript, Jest, React, Enzyme 给出示例。关于单元测试的基本概念和重要性不在本文讨论范围。
编写单元测试的基本方法其实很简单:
而一个好的单元测试的要求也很简单:
一个最简单的例子:
// `add.ts`
function add(a: number, b: number): number {
return a + b;
}
// `add.spec.ts`
test("add(1, 2) should return 3", () => {
expect(add(1, 2)).toBe(3);
});
我们编写单元测试的步骤如下:
1
, 2
add(...)
expect(...).toBe(3)
是不是很简单?当然,真实业务场景下我们要测试的单元远比上述例子复杂得多。
这里提到的输入、输出不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入,将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后,我们就可以化繁为简,将测试过程回归到前面提到的最基本的方法上。
所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出,掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。
足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种:
编写测试覆盖它们的复杂度依次增大。除了第一个,其它都可以看作外部事件,也可以理解为来自外界的副作用。对于普通变量参数,我们只需构造这些参数即可完成给定输入的任务。而对于外部事件,我们要做的就是想办法触发这些事件。
我们依然看一个简单的例子:
// `MyComponent.ts`
window.addEventListener("resize", () => { ... });
如何覆盖?主动发送这个事件:
// `MyComponent.spec.ts`
test("MyComponent", () => {
window.dispatchEvent(new Event("resize"));
// ...
});
再看一个例子:
// `MyComponent.tsx`
const handleChange = (value: string): void => { ... };
return <Editor onChange={handleChange} />
如何覆盖依赖组件的特定事件?主动触发依赖组件的事件:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
wrapper.find(Editor).invoke("onChange")("faked value");
// ...
});
足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种:
在测试中对它们进行断言的复杂度依次增大。除了第一个,其它都可以看作对外界的副作用。对于普通变量输出,我们只需简单地断言它的值即可。而对于对外界的副作用,我们要做的就是想办法断言这些副作用的影响。
我们继续看一个简单的例子:
// `handleClick.ts`
function handleClick(): void {
history.push("/next/url");
}
如何断言?我们可以断言副作用的影响结果:
// `handleClick.spec.ts`
test("handleClick", () => {
handleClick();
expect(history.location.pathname).toBe("/next/url");
});
有时副作用所影响的结果难以断言,或者该依赖被 Mocked,那么我们可以监视该副作用的触发点是否被正确调用了:
// `handleClick.spec.ts`
test("handleClick", () => {
const spyOnHistoryPush = jest.spyOn(history, "push");
expect(spyOnHistoryPush).toBeCalledWith("/next/url");
});
再看一个 React 组件的例子:
// `MyComponent.tsx`
const handleValidation = (valid: boolean): void => {
this.setState({ valid });
};
return (
<Form.Item className={this.state.valid ? "valid" : "invalid"}>
<Input />
</Form.Item>
);
如何断言?判断依赖组件的变化:
// `MyComponent.spec.tsx`
test("MyComponent", () => {
const wrapper = shallow(<MyComponent />);
// ... after something trigger `handleValidation()`
expect(wrapper.find(Form.Item).prop("className")).toBe("invalid");
});
始终记得要断言测试对象运行后对外界的副作用影响。
另外断言的目标应该是对外的影响,而不是内部状态,因为内部状态并不是测试对象的输出。一个错误的例子:
// `MyComponent.bad.spec.tsx`
expect(wrapper.instance().state.valid).toBe("invalid");
更简单的输入、输出让我们可以更容易地编写好的单元测试,但往往实际情况是业务需求不断增长,组件内部逻辑不断复杂化,输入输出的形式形态更加多样化,为组件编写单元测试的难度也随之陡增。
适时地重构与拆分是解决这个问题的关键。在如今的前端组件化的模式下尤为重要,合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, Redux 等主流框架和工具推崇的单向数据流盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化,从而降低编写单元测试的难度,同时提升组件集成时的信心。
编写良好的单元测试总结下来就是三条:
希望以上内容对大家有所帮助。
A população portuguesa é composta por 16,4 % com idade compreendida entre os 0 e os 14 anos, 66,2 % entre os 15 e os 64 anos e 17,4 % com mais de 65 anos, como tal, a população tem vindo a envelhecer. A esperança média de vida é de 78,04 anos. Em termos de alfabetização, 93,3 % sabem ler e escrever, tendo a taxa de analfabetismo vindo a descer ao longo dos anos.[82] O crescimento populacional situa-se nos 0,305 %, nascendo 10,45 por cada mil habitantes e falecendo 10,62 por cada mil habitantes, o que faz com que a população esteja a ser renovada, contribuindo para este facto a taxa de fertilidade que se situa nos 1,32 em 2010.[83] Portugal é um dos países com mais baixa taxa de mortalidade infantil abaixo dos 5 anos (3,7 por mil em 2010) no mundo.[84] quilométricas"
This is a link to another document. This is a link to an external page.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
You can write content using GitHub-flavored Markdown syntax.
To serve as an example page when styling markdown based Docusaurus sites.
Emphasis, aka italics, with asterisks or underscores.
Strong emphasis, aka bold, with asterisks or underscores.
Combined emphasis with asterisks and underscores.
Strikethrough uses two tildes. Scratch this.
I'm an inline-style link with title
You can use numbers for reference-style link definitions
Or leave it empty and use the link text itself.
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com/ or http://www.example.com/ and sometimes example.com (but not on GitHub, for example).
Some text to show that the reference links can follow later.
Here's our logo (hover to see the title text):
Inline-style:
Reference-style:
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
var s = "JavaScript syntax highlighting";
alert(s);
s = "Python syntax highlighting"
print(s)
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
function highlightMe() {
console.log("This line can be highlighted!");
}
Colons can be used to align columns.
Tables | Are | Cool |
---|---|---|
col 3 is | right-aligned | \$1600 |
col 2 is | centered | \$12 |
zebra stripes | are neat | \$1 |
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
Markdown | Less | Pretty |
---|---|---|
Still | renders | nicely |
1 | 2 | 3 |
Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
Quote break.
This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can put Markdown into a blockquote.
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a separate paragraph.
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the same paragraph.
This is a note
This is a tip
This is important
This is a caution
This is a warning
You can write JSX and use React components within your Markdown thanks to MDX.
Docusaurus green and Facebook blue are my favorite colors.I can write Markdown alongside my JSX!
5 minutes to learn the most important Docusaurus concepts.
Add Markdown or React files to src/pages to create a standalone page:
Documents are groups of pages connected through:
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Docusaurus supports Markdown and a few additional features.
Docusaurus is a static-site-generator (also called Jamstack).
You have just learned the basics of Docusaurus and made some changes to the initial template.
Let's discover Docusaurus in less than 5 minutes.
Get started by creating a new site.
Or try Docusaurus immediately with docusaurus.new.
Generate a new Docusaurus site using the classic template.
The classic template will automatically be added to your project after you run the command:
npm init docusaurus@latest my-website classic
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
The command also installs all necessary dependencies you need to run Docusaurus.
Run the development server:
cd my-website
npm run start
The cd
command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
The npm run start
command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
Open docs/intro.md
(this page) and edit some lines: the site reloads automatically and displays your changes.
This is a React page
You have just learned the basics of Docusaurus and made some changes to the initial template.
Docusaurus has much more to offer!
Have 5 more minutes? Take a look at versioning and i18n.
Anything unclear or buggy in this tutorial? Please report it!
Docusaurus creates a page for each blog post, but also a blog index page, a tag system, an RSS feed...
Create a file at blog/2021-02-28-greetings.md
:
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much you like.
A new blog post is now available at http://localhost:3000/blog/greetings
.
Documents are groups of pages connected through:
Create a markdown file at docs/hello.md
:
# Hello
This is my **first Docusaurus document**!
A new document is now available at http://localhost:3000/docs/hello
.
Docusaurus automatically creates a sidebar from the docs
folder.
Add metadata to customize the sidebar label and position:
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
It is also possible to create your sidebar explicitly in sidebars.js
:
module.exports = {
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
};
Add Markdown or React files to src/pages
to create a standalone page:
src/pages/index.js
-> localhost:3000/
src/pages/foo.md
-> localhost:3000/foo
src/pages/foo/bar.js
-> localhost:3000/foo/bar
Create a file at src/pages/my-react-page.js
:
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
A new page is now available at http://localhost:3000/my-react-page
.
Create a file at src/pages/my-markdown-page.md
:
# My Markdown page
This is a Markdown page
A new page is now available at http://localhost:3000/my-markdown-page
.
Docusaurus is a static-site-generator (also called Jamstack).
It builds your site as simple static HTML, JavaScript and CSS files.
Build your site for production:
npm run build
The static files are generated in the build
folder.
Test your production build locally:
npm run serve
The build
folder is now served at http://localhost:3000/
.
You can now deploy the build
folder almost anywhere easily, for free or very small cost (read the Deployment Guide).
Docusaurus supports Markdown and a few additional features.
Markdown documents have metadata at the top called Front Matter:
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
## Markdown heading
Markdown text with [links](./hello.md)
Regular Markdown links are supported, using url paths or relative file paths.
Let's see how to [Create a page](/create-a-page).
Let's see how to [Create a page](./create-a-page.md).
Result: Let's see how to Create a page.
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (static/img/docusaurus.png
):
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well, as shown in the extra guides.
Markdown code blocks are supported with Syntax highlighting.
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return (
<h1>Hello, Docusaurus!</h1>
)
}
```
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
Docusaurus has a special syntax to create admonitions and callouts:
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
Use this awesome feature option
This action is dangerous
MDX can make your documentation more interactive and allows using any React components inside Markdown:
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is Docusaurus green !
This is Facebook blue !
Docusaurus can manage multiple versions of your docs.
Release a version 1.0 of your project:
npm run docusaurus docs:version 1.0
The docs
folder is copied into versioned_docs/version-1.0
and versions.json
is created.
Your docs now have 2 versions:
1.0
at http://localhost:3000/docs/
for the version 1.0 docscurrent
at http://localhost:3000/docs/next/
for the upcoming, unreleased docsTo navigate seamlessly across versions, add a version dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'docsVersionDropdown',
},
],
},
},
};
The docs version dropdown appears in your navbar:
It is possible to edit versioned docs in their respective folder:
versioned_docs/version-1.0/hello.md
updates http://localhost:3000/docs/hello
docs/hello.md
updates http://localhost:3000/docs/next/hello
Let's translate docs/intro.md
to French.
Modify docusaurus.config.js
to add support for the fr
locale:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
Copy the docs/intro.md
file to the i18n/fr
folder:
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
Translate i18n/fr/docusaurus-plugin-content-docs/current/intro.md
in French.
Start your site on the French locale:
npm run start -- --locale fr
Your localized site is accessible at http://localhost:3000/fr/
and the Getting Started
page is translated.
In development, you can only use one locale at a same time.
To navigate seamlessly across languages, add a locale dropdown.
Modify the docusaurus.config.js
file:
module.exports = {
themeConfig: {
navbar: {
items: [
{
type: 'localeDropdown',
},
],
},
},
};
The locale dropdown now appears in your navbar:
Build your site for a specific locale:
npm run build -- --locale fr
Or build your site to include all the locales at once:
npm run build
An offline/local search example using @easyops-cn/docusaurus-search-local.
A Result You Can Trust.
Dozens of languages supported, including 中文分词 🇨🇳.
Looks pretty good, actually just like the Algolia Search on Docusaurus v2 website.