Wednesday, September 15, 2021

WIP: permutation engine

A while back I showed a storybook component showing a 2D grid of combinations for props for a component library. V2 looked a little less cool but did a much better job of coming up with ALL permutations, not just the 1+1 combos you can show on a grid...



Here is the code:

import React from 'react';
import PropTestingProps from './permutation-testing.props';
import { PermutationTestingStyle } from './permutation-testing.style';
import { Button } from '../Button/button.component';

import { Checkbox } from '../Checkbox/checkbox.component';
import { Radio } from '../Radio/radio.component';
import styled from 'styled-components';
import { IconButton } from '../IconButton/icon-button.component';
import { Slider } from '../Slider/slider.component';
import { Tab } from '../Tab/tab.component';
import { TextArea } from '../TextArea/text-area.component';
import { TextField } from '../TextField/text-field.component';
import { Dropdown } from '../Dropdown/dropdown.component';
import { TypeAhead } from '../TypeAhead/type-ahead.component';

const Table = styled.table`
  th {
    font-weight: bold;
  }
  th,
  td {
    padding: 8px;
    font-family: sans-serif;
    vertical-align: middle;
    text-align: left;
  }
  margin-bottom: 16px;
`;

let count = 0;
interface ComponentPropMatrixProps {
  label: string;
  permutations: string[];
  render: React.FC;
}

const getAllSubsets = (theArray: string[]) =>
  theArray.reduce(
    (subsets, value) => subsets.concat(subsets.map((set) => [value, ...set])),
    [[] as string[]],
  );

const ComponentPropSubsetsList = (props: ComponentPropMatrixProps) => {
  const { permutations, render, label } = props;

  // get all subsets, sort on length of subset, then keep original order of elemts
  const subsets = getAllSubsets(permutations)
    .sort((a, b) => a.length - b.length)
    .map((arr) =>
      arr.sort((a, b) => permutations.indexOf(a) - permutations.indexOf(b)),
    );

  return (
    <>
      <h3>{label}</h3>
      <Table>
        <tbody>
          {subsets.map((subset) => {
            const subsetAsProps = {};
            subset.forEach((x) => {
              subsetAsProps[x] = true;
            });
            count++;
            console.log(count);
            return (
              <tr key={JSON.stringify(subset)}>
                <td>{render({ componentProps: subsetAsProps, label })}</td>
                <th>{subset.join(' ')}</th>
              </tr>
            );
          })}
        </tbody>
      </Table>
    </>
  );
};
// idle, checked, error, disabled

const buttonVariations: React.ReactChild[] = [];
for (const shape of ['rectangle', 'oval']) {
  for (const size of ['sm', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      console.log(`${size} ${shape} ${ord}`);
      buttonVariations.push(
        <ComponentPropSubsetsList
          label={`Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <Button
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Button
            </Button>
          )}
        />,
      );
    }
  }
}

const checkboxVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  checkboxVariations.push(
    <ComponentPropSubsetsList
      label={`Checkbox ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Checkbox size={size} {...componentProps} label='Checkbox' />
      )}
    />,
  );
}

const iconButtonVariations: React.ReactChild[] = [];
for (const shape of ['square', 'circle']) {
  for (const size of ['sm', 'md', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      iconButtonVariations.push(
        <ComponentPropSubsetsList
          label={`Icon Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <IconButton
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Icon Button
            </IconButton>
          )}
        />,
      );
    }
  }
}

const radioVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  radioVariations.push(
    <ComponentPropSubsetsList
      label={`Radio ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Radio
          size={size}
          {...componentProps}
          id={`${JSON.stringify(componentProps)}_${size}`}
          label='Checkbox'
        />
      )}
    />,
  );
}

const sliderVariations: React.ReactChild[] = [];

sliderVariations.push(
  <ComponentPropSubsetsList
    label={`Slider`}
    permutations={[]}
    render={({ componentProps, label }) => (
      <Slider {...componentProps} label='Slider' />
    )}
  />,
);

const tabVariations: React.ReactChild[] = [];

tabVariations.push(
  <ComponentPropSubsetsList
    label={`Tab`}
    permutations={[]}
    render={({ componentProps, label }) => <Tab {...componentProps}>Tab</Tab>}
  />,
);

const textAreaVariations: React.ReactChild[] = [];

textAreaVariations.push(
  <ComponentPropSubsetsList
    label={`Text Area`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextArea {...componentProps}>Some Text</TextArea>
    )}
  />,
);

const textFieldVariations: React.ReactChild[] = [];

textFieldVariations.push(
  <ComponentPropSubsetsList
    label={`Text Field`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextField value='Some Text' {...componentProps}></TextField>
    )}
  />,
);

const dropDownVariations: React.ReactChild[] = [];

dropDownVariations.push(
  <ComponentPropSubsetsList
    label={`Dropdown`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <Dropdown value='Some Text' {...componentProps}></Dropdown>
    )}
  />,
);

const typeAheadVariations: React.ReactChild[] = [];

typeAheadVariations.push(
  <ComponentPropSubsetsList
    label={`Type Ahead`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TypeAhead value='Some Text' {...componentProps}></TypeAhead>
    )}
  />,
);

export const PermutationTesting = (props: PropTestingProps) => {
  return (
    <PermutationTestingStyle>
      {buttonVariations}
      {checkboxVariations}
      {iconButtonVariations}
      {radioVariations}
      {sliderVariations}
      {tabVariations}
      {textAreaVariations}
      {textFieldVariations}
      {dropDownVariations}
      {typeAheadVariations}
    </PermutationTestingStyle>
  );
};

As maybe you can see it's pretty easy to spin up new variants. "getAllSubsets()" is some very clever code I found online and honestly don't quite understand that makes all the permutations possible.

No comments:

Post a Comment