Skip to content

Commit

Permalink
feat: data-driven testing template
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed May 4, 2021
1 parent 34a5f96 commit 249db17
Show file tree
Hide file tree
Showing 57 changed files with 1,817 additions and 759 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
"name": "jest cc-cli",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"cwd": "${workspaceFolder}/plugins/cc-cli",
"args": ["store-enzyme-esm"],
"args": ["data-template-bundle"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
Expand Down
7 changes: 7 additions & 0 deletions core/core/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ export interface Component {
*/
jest?: JestTests;
}

/**
* list of components used in store
*/

export type Components = Record<string, Component>;

/**
* given a component, return its name
* @param component a string component name, or a component class, with a name or displayName static property
Expand Down
1 change: 1 addition & 0 deletions core/core/src/controls-randomize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './controls';
import { deepmerge } from './deepMerge';

export const randomizeSeed = (seed: number): void => faker.seed(seed);
const arrayElements = (arr: any[], c?: number) => {
const array = arr || ['a', 'b', 'c'];
let count = 0;
Expand Down
76 changes: 73 additions & 3 deletions core/core/src/controls-utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import escape from 'escape-html';
import { deepmerge } from './deepMerge';
import { deepmerge, deepMergeReplaceArrays } from './deepMerge';
import { Components, getComponentName } from './components';
import { Story, Document } from './document';
import { SmartControls } from './common';
import { controlsFromProps } from './controls-smart';
import {
ComponentControl,
ComponentControls,
ComponentControlArray,
ControlTypes,
} from './controls';

import { Story } from './document';

const mergeValue = (control: ComponentControl, value: any): any => {
if (typeof control === 'object' && control.type === ControlTypes.OBJECT) {
const objValue = mergeControlValues(
Expand Down Expand Up @@ -363,3 +365,71 @@ export const transformControls = (
}, {})
: undefined;
};

/**
* create controls for a story
* @param story - the story for which to create controls. Will check if the story accepts any arguments
* @param doc - the document to which the story belongs
* @param components - the cache of components
* @returns a collection of controls, or undefined
*/
export const getStoryControls = (
story: Story,
doc: Document,
components: Components,
): ComponentControls | undefined => {
const { controls: storyControls } = story;
if (!story.arguments?.length) {
//story has no arguments
return transformControls(storyControls);
}
const smartControls: SmartControls = story.smartControls || {};

let componentName = getComponentName(story.component);
if (
!componentName ||
((!doc.componentsLookup ||
!components[doc.componentsLookup[componentName]]) &&
typeof doc.component === 'string')
) {
componentName = doc.component as string;
}
if (componentName) {
const component = doc.componentsLookup
? components[doc.componentsLookup[componentName]]
: undefined;

if (component?.info) {
const newControls = controlsFromProps(component.info.props);
const { include, exclude } = smartControls;
const usedProps: string[] | undefined = Array.isArray(
story.arguments[0].value,
)
? story.arguments[0].value.map(v => v.name as string)
: undefined;
const filteredControls = Object.keys(newControls)
.filter(key => {
if (Array.isArray(include) && !include.includes(key)) {
return false;
}
if (Array.isArray(exclude) && exclude.includes(key)) {
return false;
}
if (usedProps && !usedProps.includes(key)) {
return false;
}
return true;
})
.reduce((acc, key) => ({ ...acc, [key]: newControls[key] }), {});
const transformed = transformControls(storyControls, filteredControls);
const { smart = true } = smartControls;
if (!story.component || !smart || story.smartControls === false) {
return transformControls(storyControls, filteredControls);
}
return transformed
? deepMergeReplaceArrays(filteredControls, transformed)
: filteredControls;
}
}
return transformControls(storyControls);
};
7 changes: 1 addition & 6 deletions core/core/src/document.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */
import { PropsWithChildren, ReactElement } from 'react';
import { CodeLocation, PackageInfo, StoryRenderFn } from './utility';
import { Component } from './components';
import { Components } from './components';
import { StoryProps } from './common';
import { ComponentControl } from './controls';
import { RunConfiguration, DocType, PageLayoutProps } from './configuration';
Expand Down Expand Up @@ -303,11 +303,6 @@ export type DocInfo = Pick<
rawTags?: string[];
rawType?: string;
};
/**
* list of components used in stories
*/

export type Components = Record<string, Component>;

/**
* list of story files, or groups
Expand Down
6 changes: 5 additions & 1 deletion core/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export * from './document-utils';
export * from './files';
export * from './jest';
export * from './utility';
export { randomizeData, canRandomizeControl } from './controls-randomize';
export {
randomizeData,
canRandomizeControl,
randomizeSeed,
} from './controls-randomize';
export * from './controls-smart';
export * from './search';
export * from './source';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { VariantButton, VariantButtonProps } from './VariantButton';
export default {
title: 'VariantButton',
component: VariantButton,
values: './VariantButton.controls.ts',
testData: './VariantButton.data.ts',
} as Document;

export const overview: Example<VariantButtonProps> = props => (
Expand Down
4 changes: 2 additions & 2 deletions core/store/src/serialization/load-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
PageLayoutProps,
mapDynamicStories,
mergeStoryProps,
getStoryControls,
} from '@component-controls/core';
import { LoadingStore } from '@component-controls/loader';
import { render as reactRender } from '@component-controls/render/react';
import { getControls } from './transform-controls';

export { LoadingStore };

Expand Down Expand Up @@ -82,7 +82,7 @@ export const loadStore = (store: LoadingStore, building?: boolean): Store => {
//storybook compat
story.controls = story.controls || (story as any).args;
Object.assign(story, mergeStoryProps(doc, story));
story.controls = getControls(story, doc, loadedComponents);
story.controls = getStoryControls(story, doc, loadedComponents);
if (!doc.stories) {
doc.stories = [];
}
Expand Down
72 changes: 0 additions & 72 deletions core/store/src/serialization/transform-controls.ts

This file was deleted.

1 change: 1 addition & 0 deletions plugins/cc-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"docs": "ts-md",
"fix": "yarn lint --fix",
"lint": "yarn eslint . --ext mdx,ts,tsx",
"create-bundle": "ccc -c test/.config -d test/bundle",
"prepare": "yarn build",
"test:create": "cc-cli -g story -c ./test/.config -o test -w",
"test": "yarn jest -i"
Expand Down
9 changes: 9 additions & 0 deletions plugins/cc-cli/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,13 @@ export const jestCliArgs: ArgOptions = [
type: 'boolean',
},
},
{
name: 'data',
options: {
alias: 'd',
description: '[n] rows to generate with data-driven testing files',
default: 0,
type: 'number',
},
},
];
2 changes: 1 addition & 1 deletion plugins/cc-cli/src/cli/cli-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CliOptions } from './types';
import { saveTemplate } from './save-template';
import { createStoreTemplate } from '../store-template';
import { createStoreTemplate } from '../jest-templates/store-template';

/**
* cli function to create a test file with dynamic tests for the entre store
Expand Down
4 changes: 2 additions & 2 deletions plugins/cc-cli/src/cli/cli-story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { loadStore } from '@component-controls/store';
import { StoryTemplateOptions, TeplateFormats } from '../types';
import { CliOptions } from './types';
import { saveTemplate } from './save-template';
import { createStoriesTemplate } from '../stories-template';
import { createDocumentTemplate } from '../document-template';
import { createStoriesTemplate } from '../jest-templates/stories-template';
import { createDocumentTemplate } from '../jest-templates/document-template';

const formatExtensions: { [key in TeplateFormats]: string } = {
cjs: '.js',
Expand Down
44 changes: 44 additions & 0 deletions plugins/cc-cli/src/data-templates/data-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import dot from 'dot';
import { getStoryControls, randomizeData } from '@component-controls/core';
import { prettify } from '@component-controls/instrument';
import { StoryTemplateOptions } from '../types';
import { getStore } from '../templating/store';
import { getTemplate } from '../templating/resolve-template';

export const createDataTemplate = async (
options: StoryTemplateOptions,
): Promise<string> => {
const {
name,
bundle,
storyPath = '',
output,
format = 'ts',
data: numValues = 0,
} = options;
if (numValues <= 0) {
return '';
}
const parsed = await getStore({ bundle, name, storyPath });
if (!parsed) {
return '';
}
const { doc, stories, components } = parsed;
const data: string[] = [];
Object.keys(stories).forEach(storyId => {
const story = stories[storyId];
const controls = getStoryControls(story, doc, components);
if (controls) {
const values: Record<string, any> = {};
for (let i = 0; i < numValues; i += 1) {
values[i.toString()] = randomizeData(controls);
}
const vars = {
story: storyId,
values: JSON.stringify(values, null, 2),
};
data.push(dot.template(getTemplate(`data-templates/data`, format))(vars));
}
});
return prettify(data.join('/n'), {}, output);
};
2 changes: 2 additions & 0 deletions plugins/cc-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './jest-templates/store-template';
export * from './jest-templates/stories-template';
Loading

0 comments on commit 249db17

Please sign in to comment.