mainboard-core-test2 v0.0.1-alpha.1
MainboardUI Documentation v1.1
This Markdown file contains all the essential details about our project. It contains information about the bugs we encountered and the standards we are going to maintain.
As a developer working on this project, you must adhere to all the standards set. This will ensure consistent code for everyone to work with.
Standard Practices:
The Project Structure
The project structure for our library separates out component code from the code that tests the components and the visual documentation using Storybook.
Global Level Code
Code pieces that we need throughout the project. These mostly include helper functions, config files and types
Top Level Code
Includes the code directly under the src/
directly. We have placed our global level components here. Global level components are intended to be directly used independent of any other component.
Module Level Code
A module is a collection of files that combine to create one complex component. For instance, to create a Header component, you need to have specific wrappers, and drawer. These components do not make sense on their own so we include it as a module-level component and not global.
These ideas extend not to components; but utilities as well. As seen below, for module utilities we use _utils
instead of @/utils
at the global level.
Depiction for our project structure:
library
│ tsconfig.json
│ package.json
| .storybook
| jest.config.ts
│ // other config files...
|
└─── src
│ │ global.d.ts // declarations
│ │ types.ts
| | index.ts
| | other global files needed throughout project...
| |
| |
| |
| |─── @/utils (global level @/utils)
| | <filename>.ts(x?) per helper
| |
| |
│ └─── Component
│ | │ Global-Level-Component.tsx
| | | index.ts
| | | types.ts
| |
| | // other similar Components...
| |
│ └─── Module
│ │ Module-Level-Component-1.tsx
│ │ Module-Level-Component-2.tsx
│ │ Module-Level-Component-3.tsx
| | types.ts
| | _utils (module level @/utils)
| <filename>.ts(x?) per helper
|
| // other similar Modules...
|
└─── tests
| | test-setup.ts
| | helper.tsx
| |
│ └─── Component
| | Component.test.tsx
| |
│ └─── Module
| | Component-1.test.tsx
| | Component-2.test.tsx
| |
|
| // tests for other components or modules
|
└─── stories
| | types.ts
| | _utils
| | <filename>.ts(x?) per helper
| |
| | // follow similar structure as in tests/ and src/
|
└─── @/utils
<filename>.ts(x?) per helper
Remember that for each module in src/
there is a index.ts
that is meant to export all the components and needed helper's (if any) from that module. Then from the root of src/
we have index.ts
that is meant to export everything from the core/root.
TypeScript Developer Experience
We rely heavily on TypeScript. Everything should be typed properly with no any
's. This ensures consistency and Storybook documentations are better as well.
React Component Declaration
You must use React.FC<Props>
to create a component. This works most consistently with our custom withStyles
HOC explained in the styling section.
Creating JSDOCs
for each component and each of its props
This is essential. Storybook uses JSDocs
to document the the Docs page on Storybook.
Storybook
Export and use unstyled component in Storybook
In <component>.stories.tsx
, in the default exported object, the component
field should be the unstyled component
(without the withStyles
HOC wrapped). This ensures props
are documented in Storybook.
The component structure
Stay consistent with the code project structure in the component structure in Storybook.
Testing
Before making unit test rebase on REACTMOD-125
It's important for waitFor and test coverage working.
The data-attribute for testing
By default we use:
data-testid
for unit-testing with React Testing Library.data-cy
for testing with Cypress.
Async testing
For async
testing it is important to wrap function with async
and wrap waitFor
with await
.
In case if waitFor
without async/await
it is ignore result of expect
test.
// ✅ correct way!
test('Example',async()=>{
await waitFor(()=>expect()
})
Mobile screen testing
For screen resize use resizeTo
function.
const resizeTo = (width, height)=>{
Object.assign(window, {
innerWidth: width,
innerHeight: height,
outerWidth: width,
outerHeight: height,
}).dispatchEvent(new window.Event('resize'));
};
For components which use useMediaQuery
.
window.matchMedia
needs to be reassigned , follow instruction from
https://mui.com/components/use-media-query/#basic-media-query.
Styling
How our styling is going to work:
We use Material-UI
's createStyles
to define the styles for the components. We have created a special withStyles
HOC that internally uses Material-UI
's withStyles
. The purpose of our withStyles
is to give a displayName
to the component which is then used in Storybook docs.
Theme Overrides:
Material-UI
provides theme overrides for its components. We are going to use that for our components as well. Theme overrides would allow us to inject styles for components' classes straight from an API call.
We have typed the custom theme overrides
object for our custom classes but we are looking to improve and automate it.
Material-UI
's Theme overrides bug:
Material-UI
has a bug where if:
The classes defined in
createStyles
depend onprops
for the component, instead of wrapping the whole object in a callback that acceptsprops
, manually set each property in that class based onprops
:const styles = createStyles({ // ❌ wrong, theme override would not work! Appbar: ({ backgroundColor }: BaseHeaderProps) => ({ backgroundColor }), // ✅ this is the way to go! Appbar: { backgroundColor: props => props.backgroundColor; }, });
Then the theme
overrides would not take place.
There is a complication encountered with this approach which we are still trying to fix. When trying to modify classes at certain breakpoints, we cannot use the breakpoint
prop
passed to the component.
As an example below, the HeaderLogoWrapper
class needs to change depending on the breakpoint
set. It is not possible to adhere to our fix/workaround as shown above. Instead you would have to wrap the whole class object in a callback to access the breakpoint
HeaderLogoWrapper: (props: { breakpoint: Breakpoint; drawerPosition: 'left' | 'right' }) => ({
[theme.breakpoints.down(props.breakpoint)]: {
order: props => (props.drawerPosition === 'left' ? 1 : 0),
},
}),
We will use this approach unless we find a better way.
Workflow
Out workflow uses Bitbucket to version control our code, and Jira to organize and assign tasks to developers. Jira and Bitbucket allow a tight and seamless integration.
We have a master
branch which is the production code.
We have a dev
branch which is a staging branch.
For features, bugfixes and other reasons, we create a specific branch from the dev
branch. The naming convention is typically <issue-type>/jira-issue-title
.
Steps to take
You will be assigned a task on Jira under the To do
swimlane. You are expected to create a Developement branch from the dev
branch on the core repository. Now, move the issue to the Development
swimlane.
You will implement the feature, or fix the bug on your branch. Once ready to commit, push the code to your branch and open a PR. Now, move the issue to the Testing
swimlane.
Your PR will run the build pipeline on Bitbucket which has a preview link to Storybook. Make sure everything looks good.
Make sure to assign reviewers to your PR. Ala will merge the PR if all is well.
Once the PR is created, go back to the Jira Issue and mention Ala or Osama in the comments.
Possible problems
Once you create a branch you will notice that yarn storybook
generates an error. This is because a jest
add-on expoects the .jest-test-results.json
to be present which is in project's .gitignore
.
If you encouter this problem, please run yarn test:generate-output
before running Storybook.
Updating branches with Git
Since we have a base dev
branch and all the feature branches are created from this, we often face syncing issues amongst the branches. For example, a developer will push their feature branch onto dev
while other developers are still working on their branches.
To re-sync your feature branch with the updated dev
branch, we make use of git rebase
. Rebase strategy allows us to maintain clean commit history.
The Git Workflow
For every feature, create a branch from the latest dev
branch.
Develop your feature, and once you develop it, make sure it is leveled with the dev
branch. For this, checkout
to the local dev
branch on your machine, and run git pull
. Now checkout
to your feature branch and run git rebase dev
. Resolve any conflicts while re-anchoring your feature branch with the head of the dev
branch.
Once resolved, create a PR. Now, it is Ala's job or anyone who is responsible to merge it to dev
to rebase dev
branch to this feature branch. Simply checkout to dev
and run git rebase <feature-branch>
to put the feature branch's commits on top of the local dev
branch. Then push the changes.
Checkout this video to understand the exact workflow we are using.
Note: Once you rebase your feature branch, you usually have to use git push -f
to forcefully push your changes.
This documentation is constantly being maintained and improved.
Changelog
v1.1
- Include Git Workflow.
- Update the project's directory structure.
2 years ago