Skip to content

Commit

Permalink
Added storybook
Browse files Browse the repository at this point in the history
- fixed invalid type placeholder
- added stories-check
- added support for Provider type overriding
  • Loading branch information
AleksandarDev committed Mar 24, 2022
1 parent 883ed6f commit 97d6c52
Show file tree
Hide file tree
Showing 17 changed files with 10,842 additions and 565 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/stories-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Stories check

on:
pull_request:
branches:
- main

jobs:
checkScreenshots:
name: Stories check

runs-on: windows-2022

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: "16.x"
cache: "yarn"

- name: Yarn install
run: yarn install --network-timeout 1000000

- name: Run stories-check
id: storiesCheck
continue-on-error: true
run: yarn stories-check

- name: Commit story changes
id: csc
if: steps.storiesCheck.outcome == 'failure'
uses: EndBug/add-and-commit@v8
with:
add: ".stories-approved"
message: "[skip ci] [stories-check] Automated story changes"
new_branch: ${{ github.head_ref }}
default_author: github_actions

- name: Run cypress-check
id: cypressCheck
continue-on-error: true
run: yarn cypress-check

- name: Commit cypress changes
id: ccc
if: steps.cypressCheck.outcome == 'failure'
uses: EndBug/add-and-commit@v8
with:
add: ".cypress-approved"
message: "[skip ci] [cypress-check] Automated cypress changes"
new_branch: ${{ github.head_ref }}
default_author: github_actions
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
/cypress/screenshots
/cypress/videos

# storybook
/storybook-static

# storycap
/.stories-pending

# production
/lib

Expand Down
17 changes: 17 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const path = require('path');

module.exports = {
stories: [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"storycap"
],
framework: "@storybook/react",
core: {
"builder": "webpack5"
}
}
10 changes: 10 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
layout: 'centered'
}
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ import { FormBuilderField } from '@enterwell/react-form-builder';
</FormBuilder>
```
#### Override components
You can nest `FormBuilderProvider` inside other provider. The bottom provider will take all components from its parent and override matching with newly provided component types.
```js
<FormBuilderProvider components={formComponentsA}>
<FormBuilderProvider components={formComponentsB}>
<!-- Can use both types from A and B, if B contains same type as A, B type is resolved here -->
</FormBuilderProvider>
</FormBuilderProvider>
```
## Development
For development:
Expand Down
50 changes: 50 additions & 0 deletions helpers/ci/NodeHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// General import
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const { stdout } = require('process');

/**
* Gets all the file paths from the selected directory recursively.
*
* @param {string} directory Path to the directory from which to read the files.
* @returns Array of file paths in the selected directory
*/
module.exports.getFilesRecursively = (directory) => {
const files = [];

const filesInDirectory = fs.readdirSync(directory);

filesInDirectory.forEach((file) => {
const absolute = path.join(directory, file);

if (fs.lstatSync(absolute).isDirectory()) {
files.push(...this.getFilesRecursively(absolute));
} else {
files.push(absolute);
}
});

return files;
};

/**
* Prints the information about starting the task and its completion to the console.
*
* @param {string} message Message to output before starting the process.
* @param {Function} taskToRun Function to invoke.
* @returns Value that the function returns
*/
module.exports.logProcess = (message, taskToRun) => {
try {
stdout.write(message);
const valueToReturn = taskToRun();
stdout.write(`${chalk.green(' DONE ✔')}\n`);

return valueToReturn;
} catch (error) {
stdout.write(`${chalk.red(' ERROR X')}\n`);

throw error;
}
};
76 changes: 76 additions & 0 deletions helpers/ci/ScreenshotsCompare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// General imports
const fs = require('fs-extra');
const chalk = require('chalk');

// Node helpers import
const { getFilesRecursively, logProcess } = require('./NodeHelpers');

/**
* Scripts main function.
*
* @param {string} approvedPath Path to the approved screenshots directory
* @param {string} pendingPath Path to the pending screenshots directory
* @param {string} approvedDirName Name of the approved screenshots directory
* @param {string} pendingDirName Name of the pending screenshots directory
*/
const compare = (approvedPath, pendingPath, approvedDirName, pendingDirName) => {
try {
let numberOfChanges = 0;

// Ensure the approved directory exists.
fs.ensureDirSync(approvedPath);

// Get all the approved files.
const approvedFiles = logProcess('Fetching approved files...', () => getFilesRecursively(approvedPath));

// Get all the pending files.
const pendingFiles = logProcess('Fetching pending files...', () => getFilesRecursively(pendingPath));

// Iterate all the approved files.
approvedFiles.forEach((approvedFile) => {
const approvedFileShort = approvedFile.substring(approvedFile.indexOf(approvedDirName) + approvedDirName.length + 1);
const possiblePending = approvedFile.replace(approvedDirName, pendingDirName);

// If the approved file is an old one (does not exist in the pending folder)
// delete it from the approved folder.
if (!pendingFiles.includes(possiblePending)) {
logProcess(`\t${chalk.red(`[-] ${approvedFileShort}`)}`, () => fs.removeSync(approvedFile));
numberOfChanges++;
}
});

// Iterate all the files that are pending for approval.
pendingFiles.forEach((pendingFile) => {
const possibleNew = pendingFile.replace(pendingDirName, approvedDirName);
const possibleNewShort = possibleNew.substring(possibleNew.indexOf(approvedDirName) + approvedDirName.length + 1);

// If the pending file is a new one (does not exist in the approved folder)
// move it to the approved folder.
if (!approvedFiles.includes(possibleNew)) {
logProcess(`\t${chalk.green(`[+] ${possibleNewShort}`)}`, () => fs.moveSync(pendingFile, possibleNew, { overwrite: true }));
numberOfChanges++;
} else {
const pendingFileBytes = fs.readFileSync(pendingFile);
const approvedFileBytes = fs.readFileSync(possibleNew);

// If the files are not the same, overwrite it.
if (!pendingFileBytes.equals(approvedFileBytes)) {
logProcess(`\t${chalk.yellow(`[≠] ${possibleNewShort}`)}`, () => fs.moveSync(pendingFile, possibleNew, { overwrite: true }));
numberOfChanges++;
} else {
console.log(`\t${chalk.white(`[=] ${possibleNewShort}`)}`);
}
}
});

console.log(`Detected changes: ${numberOfChanges}`);

if (numberOfChanges) process.exit(1);
} catch (error) {
console.error(chalk.red('Error occurred while running the script:'));
console.error(chalk.red(error));
process.exit(2);
}
};

module.exports = { compare };
29 changes: 29 additions & 0 deletions helpers/ci/StoriesCompare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// General imports
const path = require('path');

// Compare script import
const { compare } = require('./ScreenshotsCompare');

// Directory paths
const APPROVED_DIR_NAME = '.stories-approved';
const PENDING_DIR_NAME = '.stories-pending';

const APPROVED_STORIES = path.join(__dirname, `../../${APPROVED_DIR_NAME}`);
const PENDING_STORIES = path.join(__dirname, `../../${PENDING_DIR_NAME}`);

/**
* Running the script with the storybook stories parameters.
*
* @param {string} approvedStories Path to the approved stories directory
* @param {string} pendingStories Path to the pending stories directory
* @param {string} approvedDirName Name of the approved stories directory
* @param {string} pendingDirName Name of the pending stories directory
*/
const run = (
approvedStories = APPROVED_STORIES,
pendingStories = PENDING_STORIES,
approvedDirName = APPROVED_DIR_NAME,
pendingDirName = PENDING_DIR_NAME
) => compare(approvedStories, pendingStories, approvedDirName, pendingDirName);

module.exports = { run };
24 changes: 20 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"scripts": {
"clean": "rimraf dist && rimraf lib",
"dev": "npm run clean && rollup -c -w",
"build": "npm run clean && rollup -c"
"build": "npm run clean && rollup -c",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook",
"stories-check": "rimraf ./.stories-pending && storycap --serverCmd \"start-storybook --ci -p 6007\" --serverTimeout 180000 -o .stories-pending http://localhost:6007 && kill-port 6007 && node -e \"require('./helpers/ci/StoriesCompare').run()\""
},
"engines": {
"node": ">=14"
Expand All @@ -36,11 +39,21 @@
"react-dom": "^17"
},
"devDependencies": {
"@babel/core": "^7.17.8",
"@enterwell/react-form-validation": "^1.1.9",
"@rollup/plugin-commonjs": "^21.0.2",
"@rollup/plugin-node-resolve": "^13.1.3",
"@storybook/addon-actions": "^6.4.19",
"@storybook/addon-essentials": "^6.4.19",
"@storybook/addon-interactions": "^6.4.19",
"@storybook/addon-links": "^6.4.19",
"@storybook/builder-webpack5": "^6.4.19",
"@storybook/manager-webpack5": "^6.4.19",
"@storybook/react": "^6.4.19",
"@storybook/testing-library": "^0.0.9",
"@types/node": "^17.0.23",
"@types/react": "^17.0.2",
"babel-loader": "^8.2.4",
"cross-env": "^7.0.3",
"postcss": "^8.4.12",
"react": "^17.0.2",
Expand All @@ -50,6 +63,9 @@
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2",
"typescript": "^4.6.2"
}
}
"storycap": "^4.0.0-alpha.1",
"typescript": "^4.6.2",
"webpack": "^5.70.0"
},
"dependencies": {}
}
12 changes: 2 additions & 10 deletions src/FormBuilder/FormBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@ import FormBuilderContext from "./FormBuilderContext";
import { submitForm } from '@enterwell/react-form-validation';
import { FormBuilderProviderContext } from "../FormBuilderProvider/FormBuilderProviderContext";
import { FormBuilderProps } from "./FormBuilder.types";

function UndefinedField({ fieldName }: { fieldName: string }) {
return <div style={{
color: '#FF8080',
padding: '12px',
backgroundColor: '#290000',
borderRadius: 8
}}>{`Undefined form field "${fieldName}"`}</div>
}
import InvalidPlaceholder from "./InvalidPlaceholder";

export default function FormBuilder(props: FormBuilderProps) {
const {
Expand Down Expand Up @@ -42,7 +34,7 @@ export default function FormBuilder(props: FormBuilderProps) {
}
return (
<FieldWrapper key={fieldName}>
<UndefinedField fieldName={fieldName} />
<InvalidPlaceholder fieldName={`empty field ${fieldName}`} />
</FieldWrapper>
);
})}
Expand Down
2 changes: 1 addition & 1 deletion src/FormBuilder/FormBuilder.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FormField } from "../index.types";
export interface FormBuilderProps {
children?: React.ReactNode | undefined;
form: FormItems;
onSubmit: (formData: object) => void;
onSubmit?: (formData: object) => void;
}

export interface FormItems {
Expand Down
12 changes: 12 additions & 0 deletions src/FormBuilder/InvalidPlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

export default function InvalidPlaceholder({ fieldName }: { fieldName: string; }) {
return (
<div style={{
color: '#FF8080',
padding: '12px',
backgroundColor: '#290000',
borderRadius: 8
}}>{`Invalid: "${fieldName}"`}</div>
);
}
4 changes: 4 additions & 0 deletions src/FormBuilderField/FormBuilderField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext } from "react";
import FormBuilderContext from "../FormBuilder/FormBuilderContext";
import InvalidPlaceholder from "../FormBuilder/InvalidPlaceholder";
import { FormBuilderProviderContext } from "../FormBuilderProvider/FormBuilderProviderContext";
import { FormField } from "../index.types";

Expand All @@ -22,6 +23,9 @@ export default function FormBuilderField(props: FormBuilderFieldsProps) {
const formContext = useContext(FormBuilderContext);
const Component = context.components[type];

if (!Component)
return <InvalidPlaceholder fieldName={`component type ${type}`} />

return (
<Component
value={value}
Expand Down
Loading

0 comments on commit 97d6c52

Please sign in to comment.