npm package: @redmountaintec/ysui
Description: Component library using React, Typescript, MUI and Storybook bundled with Rollup
Author: Brandon Allred
Latest version: 0.1.5

# Table of Contents

- [General Info](#general-info)
- [Technologies](#technologies)
- [Setup](#setup)
- [Installing Additional Dependencies](#installing-additional-dependencies)
- [Folder Structure](#folder-structure)
- [Available Scripts](#available-scripts)
  - [npm run storybook](#npm-run-storybook)
  - [npm test](#npm-test)
  - [npm run build](#npm-run-build)
- [Publishing Your Changes](#publishing-your-changes)
- [Versioning](#versioning)
- [Typescript & Constant Declarations](#typescript-&-constant-declarations)
- [Testing](#testing)
  - [Writing Tests](#writing-tests)
  - [Testing Components](#testing-components)
- [Implementation](#implementation)

# General Info

The purpose of this project is to build/provide custom components that can be implemented throughout all of our applications.

### Technologies

- React
- Typescript
- [MUI](https://github.com/mui-org/material-ui)
- npm
- Emotion
- date-fns

# Setup

Currently, development is happening with `npm` version 16 or higher.

**If you don't have npm installed, then install it locally: [nodejs](https://nodejs.org/en/download/) or via asdf [website](https://asdf-vm.com/guide/getting-started.html#_3-install-asdf)**

- If you're using asdf to control your node version for other projects, you can set your shell to version 16 via:
  ```bash
  asdf install nodejs 16.13.0
  ```
  then set your nodejs version per shell via:
  ```bash
  asdf shell nodejs 16.13.0
  ```

**Because our component library is private**, you'll need to add the `.env` variables in order to publish successfully. These variables are only valid for 30 days so chances are if these don't work, we'll need to generate a new `NPM_TOKEN`.

1. Create a .env file with the `GH_TOKEN` and `NPM_TOKEN` variables. These values can be found [here]

2. Install dependencies

   ```bash
   npm install
   ```

3. Start up Storybook

   ```bash
   npm run storybook
   ```

# Installing Dependencies

The generated project includes React, ReactDOM, Typescript, @mui/material, @emotion/react, @emotion/styled and date-fns as devDependencies. You may install other dependencies (for example, React Router) with npm:

npm install --save-dev react-router

Whenever a devDependency is installed, be sure to add it as a peer dependency as well. Our intent is to keep this component library lightweight in order to improve performance and to avoid duplication of node_modules.

# Available Scripts

In the project directory, you can run:

### `npm run storybook`

Runs the app in the development mode.<br>
Open [http://localhost:6006](http://localhost:6006) to view it in the browser.

The page will reload if you make edits.<br>
You will also see any lint errors in the console.

### `npm test`

Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](#running-tests) for more information.

### `npm run build`

Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.

# Folder structure

When building out your component, you should include a main folder (named appropriately) with all the related files nested inside.

- Required
  - index.tsx file that exports your custom component
  - [FileName].ias.ts to include your type/interface definitions
  - [FileName].test.tsx to include your tests for that component
- Optional
  - [FileName].stories.tsx to provide the UI portion of your component in storybook (not necessary for hooks/utils)
  - [FileName].testIds.ts to provide the specific testIds for that component (could test component other methods eg. screen.getByText('/save/'))

Below is an example of how your file structure should appear when creating your component or customHook:

```
my-app/
  README.md
  node_modules/
  dist/
  src/
    components/
      Button/
        Button.ias.ts
        Button.stories.tsx
        Button.test.tsx
        Button.testIds.ts
        index.tsx
    customHooks/
      useDelay/
        useDelay.test.tsx
        index.tsx
    index.ts
```

# Versioning

By default, the project is set to autoincrement the patch version upon every push/merge to the main branch. When there is a breaking change for a previous component, the minor version should be manually incremented by one in the package.json.

`[major].[minor].[patch]`

An example of a breaking change would include the following:

- Change in default behavior of a prop eg.(disabled?: boolean => disabled: boolean)
- Addition/removal of a required prop to a previously built component
- Required peerDependency for the project

When creating your pull request, you would need to increment the minor version of the pacakge.json by one

`0.1.29` would need to be changed to `0.2.0` prior to creating your pull request.

Major versions should never be changed.

# Publishing your changes

In order for your changes to be published and available via npm, your feature branch must be merged into main.
For the project to publish your changes, **your exports for the components and any interface/typings you want included must be exported from the main index.ts file in the src directory**:

1. Create a feature branch off develop

2. Make and commit the changes to your feature branch

3. Create your PR to the develop branch

4. Once your PR is merged, merge the develop branch into main

# Typescript & Constant Declarations

Typescript Identifiers - I like to follow the typescript standards listed below. You can review these in more detail if you’d like [here](https://google.github.io/styleguide/tsguide.html)

- **The one caveat is, I like prepending an I character in front of the Interface identifiers**

  1. **UpperCamelCase**
     - Interface - interface variables should be UpperCamelCase with an “I” character prepended eg. IButtonProps
     - class
     - type
     - enum
     - decorator
     - type parameters
  2. **lowerCamelCase**
     - variable
     - parameter
     - function
     - method
     - property
     - module alias
  3. **CONSTANT_CASE**
     - Global constant values
     - enum values
     - Redux Actions

# Testing

## Writing Tests

To create tests, add `it()` (or `test()`) blocks with the name of the test and its code. The `describe()` command allows you to group related tests, producing cleaner outputs.

Jest provides a built-in `expect()` global function for making assertions. A basic test could look like this:

```js
import { fireEvent, render, screen } from '@testing-library/react';
import { KlsButton } from '@youscience/khaleesi'

describe('KlsButton', () => {
  it('is disabled', () => {
     render(
      <KlsButton
        color='primary'
        disabled
        label='Hello world!'
        onClick={() => {}}
        testId="KLSBUTTON"
        variant='contained'
      />
    );
      const button = screen.getByTestId(
      "KLSBUTTON"
    ) as HTMLButtonElement;
    expect(button).toBeDisabled();
  });
})
```

All `expect()` matchers supported by Jest are [extensively documented here](https://facebook.github.io/jest/docs/en/expect.html#content).<br>
You can also use [`jest.fn()` and `expect(fn).toBeCalled()`](https://facebook.github.io/jest/docs/en/expect.html#tohavebeencalled) to create “spies” or mock functions.

## Testing Components

There is a broad spectrum of component testing techniques. They range from a “smoke test” verifying that a component renders without throwing, to shallow rendering and testing some of the output, to full rendering and testing component lifecycle and state changes.

Different projects choose different testing tradeoffs based on how often components change, and how much logic they contain. If you haven’t decided on a testing strategy yet, we recommend that you start with creating simple smoke tests for your components:

```js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});
```

This test mounts a component and makes sure that it didn’t throw during rendering. Tests like this provide a lot of value with very little effort so they are great as a starting point, and this is the test you will find in `src/App.test.js`.

When you encounter bugs caused by changing components, you will gain a deeper insight into which parts of them are worth testing in your application. This might be a good time to introduce more specific tests asserting specific expected output or behavior.

Best practices for our component unit tests, include testing component functionality and the cause/effect of actions performed on the component itself. Don't test the component with static props/variables like in the KlsButton example above, it will always be disabled. A more appropriate example would be to test what happens if the disabled button is clicked:

```js
import { fireEvent, render, screen } from '@testing-library/react';
import { KlsButton } from '@youscience/khaleesi'
import { Input } from '@mui/material'

describe('KlsButton', () => {
 it('should be disabled & not allow button to be clicked', () => {
   const onClickMock = jest.fn();
    render(
     <KlsButton
       color='primary'
       disabled
       label='Hello world!'
       onClick={onClickMock}
       testId="KLSBUTTON"
       variant='contained'
     />
   );
   const button = screen.getByTestId("KLSBUTTON") as HTMLButtonElement;
   // expect the buttton mock to not be clicked
   expect(onClickMock).toBeCalledTimes(0);
   expect(button).toBeDisabled();
   // attempt to click the button
   fireEvent.click(button);
   // expect the function to not be called even if clicked while disabled
   expect(onClickMock).not.toHaveBeenCalled();
 });
})
```

This ensures the custom component you built will not call the function provided to the button if it is disabled.

# Implementation

## Importing a Component from `@youscience/khaleesi`

**Because our component library is private, you'll need to add a `.npmrc` file in root of the project in order to have access**

1. Sign up or login to your [npm account](https://www.npmjs.com/signup)
2. Add a custom `Read Only` token to your npm account [create npm tokens](https://docs.npmjs.com/creating-and-viewing-access-tokens)

- You will get an access error if you're not invited to the @youscience organization. Reach out to Brandon Allred @ brandon.allred@youscience.com for access to the organization.

3. Create a .npmrc file in ~/$HOME/[user]
4. Add the Read Only token you generated, the registry and scope. It should look similar to this.

   ```bash
   // registry.npmjs.org/:_authToken=npm_YOUR_TOKEN_HERE
   scope=@youscience
   @youscience:registry=https://registry.npmjs.org/
   ```

5. Install @youscience/khaleesi package

   ```bash
   npm install
   ```

6. You should now be able to install the @youscience/khaleesi package & utilize the components

   ```jsx
   import { useState } from 'react'
   import { useDebounce } from '@youscience/khaleesi'
   import { TextField } from '@mui/material'

   export function Form() {
     const [name, setName] = useState('')
     const onChange = useDebounce(setName, 500)

     <TextField
       label="Name"
       onChange={onChange}
       ...
     />

   }

   ```

## License

@youscience/khaleesi is [MIT licensed](https://github.com/youscience/khaleesi/blob/main/LICENSE)