A description of the structure of the codebase, conventions being followed, and patterns.
The TDS codebase maintains a set of organizational and syntactical standards. We utilize the following tools for the development, release, and distribution processes:
- Styleguidist: isolated React component development environment with a living style guide
- Jest and Enzyme: we use jest as our test runner and enzyme to test our React components
- Linters and Prettier: standardize code style and format
- CSS Modules: facilitates the buildup of scoped CSS while maintaining the familiar interface of SCSS
- NPM: we use NPM as our node package manager
- Openshift and Docker: the CI pipeline is largely based on the TELUS isomorphic starter kit pipeline, using Docker as the build artifact
Component structure and standards
All TDS components have a common directory structure and set of standards. Where you have a
ButtonLink, the files are organized like this:
/packages/ │ └─── ButtonLink │ ButtonLink.md │ ButtonLink.jsx │ ButtonLink.modules.scss | index.cjs.js │ index.es.js | package.json | README.md | rollup.config.js | └─── __tests__ │ ButtonLink.spec.jsx | └─── __snapshots__ | ButtonLink.spec.jsx.snap
Here you may notice some of our standards:
- Use PascalCase for all file names
- Every component must include a set of unit tests and snapshot
- If a component requires custom styling, use CSS Modules and suffix your scss file with
- To include custom documentation with a component, use
- To include documentation for the npm registry page, use
Building React components for TDS involves a set of coding standards in order to maintain consistent syntax and form across the codebase. Though the majority of syntax is automatically formatted with our linter rules and with the aid of Prettier (see Developer Guide for setup), this guide covers the more subjective rules we uphold.
Creating new components from scratch
When starting fresh, you can use the scaffolding script to generate a component directory structure:
npm run scaffold [ComponentName]
This will output a set of files in the aforementioned structure.
React & JSX patterns
Though the following patterns are not strictly enforced, they are strongly encouraged:
- Prefer functional stateless components. Only create a class when you either need state or need lifecycle methods
- Accessibility and responsiveness is a must for every component
- Provide a flexible and predictable interface via props
- Components should only be aware of themselves or their direct children
- De-structure props in the function signature to make it easy to see all of the used props.
- Always accept "rest" props (except for className and style) to allow for common global HTML attributes such as
id. Spread these onto the main HTML element of the component before other props. This is to prevent accidental or purposeful overriding.
styleare also not available as a prop interface in most components because TDS strictly enforces the TELUS brand through styles, and as such there should be no potential inaccurate brand representations.
- If a parent component does not use props and only passes them down to its children, pass components as props.
TDS components use CSS Modules with Sass. You can learn about its usage and design from the CSS Modules GitHub repository. TDS components derive CSS modules from their respective ComponentName.modules.scss file. The following patterns are strongly encouraged:
- Use the
composesproperty rather than SCSS
@extendor comma-separated classes. Mixins are acceptable
- Use camelCase class names
- Use flexbox, but be aware of cross-browser limitations
- Components should make effective use of 'layout' components such as Box or Responsive, rather than styles
composes example above:
<!-- the 'class' attribute contains the 'primary' and 'base' classes since 'primary' composes' base --> <a class="primary base" href="#">Find out how</a>
Tests utilize Jest and Enzyme matchers. Tests are treated as a first-class citizen. Tests should clearly outline the features and expected output for a component. For some inspiration, have a look at how pre-existing TDS components' tests are written.
Use assertions that produce helpful error messages, enzyme-matchers is useful for this.
expect(myComponent).toHaveProp('someBoolean', true) // => Expected myComponent to have the prop "someBoolean" // with the value of true, but it was false.
expect(myComponent.props().someBoolean).toBeTruthy() // => Expected false to be truthy.
Always prefer "shallow" rendering, then "render", then "mount". Only "mount" if you are testing the lifecycle methods.
Use a snapshot test for components that do not have any logic and to increase confidence in the structure of the component. Snapshot tests do not replace unit tests.