Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | 113x 165x 113x 373x 373x 373x 373x 113x 113x 373x 373x 373x 373x 373x 373x 157x 157x 157x 373x 373x 373x 373x 373x 373x 113x 113x 276x 276x 276x 276x 2305x 613x 613x 613x 1692x 276x 276x 113x 276x 276x 9613x 1826x 1826x 276x 276x 113x 1516x 1516x 102x 1516x | import { DocsContext, getStoryId } from "@storybook/addon-docs"; import { Source } from "@storybook/components"; import React, { createContext, useContext } from "react"; import dedent from "ts-dedent"; import type { WithCy } from "../types"; /** Props for the UnitTest component, mostly to link to story */ export type UnitTestProps = { /** name matching the story's name */ name?: string; /** id matching the story's name. either name or id must be provided */ id?: string; /** * description for tests which have cy function or cyTest formats. * Provides a reasonable default but can opt out with `false` */ description?: string | false | null; /** * Parameters, probably passed in from parent in unitTestDecorator, * though I suppose passing in directly could be useful somehow */ parameters?: WithCy<any>; /** * Potentially internal only: if the literate test is from code block. Used to * deduplicate display of code block in docs view */ isCodeBlock?: boolean; }; const getDefaultDescription = (cyTest: unknown) => `should pass the following ${cyTest ? "cyTest" : "cy"} expectation`; /** Get entries to map over to then display in each Preview component */ const getNormalizedCyTestEntries = (parameters: any, props: UnitTestProps) => { Iif (!parameters) return []; const { cy, cyTest } = parameters; if (cy) { return typeof cy === "object" ? Object.entries(cy) : [[props.description ?? getDefaultDescription(false), cy]]; } Iif (cyTest) { return [[props.description ?? getDefaultDescription(true), cyTest]]; } return []; }; /** Context for parameters used in unitTestDecorator */ export const ParametersContext = createContext({}); /** * Display test information for pure unit test stories. * This is likey used in mdx files and must have a 'name' or 'id' which would appropriately * match to the proper story. * See [the task story](https://quotapath.github.io/orphic-cypress/storybook/?path=/docs/cypressutils-tasks--arbitrary-task#literate-testing) * for detailed use. * ```ts * <Story * name="ArbitraryTask" * parameters={{ * cy: () => * cy.arbitraryTask(2).then(($num) => expect($num).to.equal(2)), * }} * > * <UnitTest name="ArbitraryTask" /> * </Story> * ``` * * Or by providing the `unitTestDecorator` decorator and setting a `cyUnitTest` * parameter to `true` */ export const UnitTest = (props: UnitTestProps) => { // lots of different possible ways of getting parameters in here const docsContext = useContext(DocsContext); const parametersContext = useContext(ParametersContext); let parameters = props.parameters; const hasDocsContext = docsContext && Object.keys(docsContext).length > 0; const hasParametersContext = parametersContext && Object.keys(parametersContext).length > 0; if (!parameters) { Iif (hasDocsContext) { const storyId = getStoryId(props, docsContext); const story = docsContext .componentStories() .find(({ id }) => id === storyId); parameters = story?.parameters; } else if (hasParametersContext) { parameters = parametersContext; } } Iif (!parameters) return null; const cyMap = getNormalizedCyTestEntries(parameters, props); const previews = cyMap.map(([key, orgCode], i) => { const code = dedent((orgCode as () => void).toString()); return ( <div key={String(key) || i}> {key && <div className="orphic-cypress-unit-test">{key}</div>} {!(props.isCodeBlock && hasDocsContext) && ( <Source language="tsx" dark format={false} code={code} /> )} </div> ); }); return <>{previews}</>; }; const regex = { multilineInit: /^\s?\/\*/, multilineClose: /\*\//, comment: /^\s?(\/\/|\/\*\*|\*\/|\*)\s?/, }; const partitionCommentsAndCode = ( fnToParse: string ): [comments: string, code: string] => { const [comments, code]: [string[], string[]] = [[], []]; const fnToParseSplit = fnToParse.split("\n"); let isMultiLine = regex.multilineInit.test(fnToParseSplit[0]) && !regex.multilineClose.test(fnToParseSplit[0]); for (const line of fnToParse.split("\n")) { if (code.length === 0 && (regex.comment.test(line) || isMultiLine)) { if (regex.multilineClose.test(line)) isMultiLine = false; const newComment = line.replace(regex.comment, ""); // strip empty lines if (newComment.length) comments.push(newComment); } else { code.push(line); } } // for now at least, const joinedDescription = comments.length > 0 ? comments.join(" ") : ""; return [joinedDescription, code.join("\n")]; }; /** * Gets the code block matching a story from the mdx page. * Just a tad hacky with how its getting to the MDXContent * @private */ export const getStoryCyFromMdxCodeBlock = ( parameters: any, storyName: string, functionize?: boolean ): { [description: string]: string } => { const mdxContent = parameters?.docs?.page?.()?.props?.children?.type?.({}); for (const child of mdxContent?.props?.children ?? []) { if (child?.props?.mdxType === "pre") { const childrenProps = child.props.children?.props; if (childrenProps.metastring === storyName) { const [description, code] = partitionCommentsAndCode( childrenProps.children ); return { [description.length ? description : getDefaultDescription(false)]: functionize ? `() => { ${code} }` : code, }; } } } return {}; }; /** * A storybook decorator that provides a parameter context for the sake * of showing UnitTest components in cypress and storybook canvas as opposed * to just docs, and allows display without manually adding a UnitTest component * via `cyUnitTest` parameter. * TODO: types would be nice, but have been annoying */ export const unitTestDecorator = (Story: any, context: any) => { const parameters = { ...context.originalStoryFn, ...context.parameters }; if (parameters.cyCodeBlock) { parameters.cy = getStoryCyFromMdxCodeBlock( context.parameters, context.originalStoryFn.storyName ); } return ( <ParametersContext.Provider value={parameters}> <Story /> {(parameters.cyUnitTest || parameters.cyCodeBlock) && ( <span data-cy="cy-unit-test"> <br /> <UnitTest name={context.name} parameters={parameters} isCodeBlock={parameters.cyCodeBlock} /> </span> )} </ParametersContext.Provider> ); }; |