Download and run:
1.yarn install
2.yarn start
The project is now fully ported back to npm, please don't use yarn anymore.
Running commands with npm npm run [command]
command | description |
---|---|
start |
Starts a development server with hot module reloading |
server |
Starts a SSR server, run build and buildserver first |
build |
Creates a production bundle of the client |
buildserver |
Creates a production bundle of the server |
test |
Opens the test suite |
test:coverage |
Generates a coverage report |
test:verbose |
Runs the test suite with advanced logging |
catalog:install |
Installs styleguide dependencies |
catalog |
Starts the styleguide |
catalog:upload |
reserved for CI servers only |
lint |
Syntax check |
lint:fix |
Fix syntax as far as automatically possible |
svg |
Generates React components for all svg under ./src |
release |
reserved for CI servers only |
danger |
reserved for CI servers only |
ci:* |
reserved for CI servers only |
prefix name | description |
---|---|
no prefix | only master , development and gh-pages |
feature/xyz |
Normal working branches. Everything you change, should be in a feature branch. Feature branches are ment to be merged into master someday |
hotfix/xyz |
Reserved for fixing already released versions, the fix release will be on the hotfix branch and will also be merged into master afterwards |
test/xyz |
Throw away branches, just for testing out stuff, without the intention to ever be merged. |
- Install the Chrome Debugger Extension
- Create a new folder in project root
.vscode
- Create a new file in that folder
.vscode/launch.json
, enter this and save{ "version": "0.2.0", "configurations": [{ "name": "Chrome", "type": "chrome", "request": "launch", "url": "http://localhost:3000", "webRoot": "${workspaceRoot}/src", "userDataDir": "${workspaceRoot}/.vscode/chrome", "sourceMapPathOverrides": { "webpack:///src/*": "${webRoot}/*" } }] }
- Run
npm start
to start the devserver - Now you can set breakpoints in the code editor and run the code with
F5
for more cool stuff read the docs
- Major and Minor are only allowed on
master
- Patch releases are only allowed on
master
orhotfix/xyz
branches. If the fix is for an old (already released) version, hotfix should be used for the release. - Premajor, Preminor, Prepatch and Prerelease are only alowed on
feature/xyz
branches
target | effect |
---|---|
major |
Increments the major position: vX.0.0 |
minor |
Increments the major position: v0.X.0 |
patch |
Increments the major position: v0.0.X |
premajor |
Looks up the next major version, minor and patch will be zero. A label accoring to the current feature branch is added: vX.0.0-branch-name.0 |
preminor |
Looks up the next minor version, major is untouched and patch will be zero. A label accoring to the current feature branch is added: v0.X.0-branch-name.0 |
prepatch |
Looks up the next patch version, major and minor are untouched. A label accoring to the current feature branch is added: v0.0.X-branch-name.0 |
prerelease |
Increases the last number of the precending release tag: v0.0.0-branch-name.X Note: This assumes, you have already an precending prerelease tag present. If not: the effect is the same as prepatch. |
All changes should be logged in CHANGELOG. There are two ways to do this:
Running release xyz
looks for a heading level 2 with the new version number. If such header is present, everything between this header and the next level 2 header is used as changelog.
If no such header is found, an editor will open with some markdown of all commit messages since the last tag in the current branch
The project structure tries to follow the common best practice around create-react-app.
The only place where ES6 works
.
├── App.js // main application entry point (future routing happens here)
├── configureStore.js // wrapps the root redoucer and adds redux middlewares
├── constructors // global classes, used in the whole app
│ ├── ApiClient
│ └── Translator
├── data // all child reducer live here
│ ├── locations
│ └── reducer.js
├── donutStyles.js // development only styles, during the donut hole phase
├── globalStyles.js // global style sheet (body tag, fonts, ...)
├── index.js // startup, the only place where the DOM get's accessed directly
├── pages // top level pages
│ ├── Home
│ └── ...
├── reducer.js // the root reducer
├── setupTests.js // this file runs before every test (currently it enables enzyme)
└── styleguide // think about styleguilde as a separate module
├── components // styleguide components
├── images // global images
├── mixins // functions to be used inside styled components styles
├── theme // configuration variables, mostl for styles
└── util // styleguide utilities (setName() function)
Every directory should have an index.js. Here are the different kind of index.js files in a component directory.
.
├── Radio.js // component
├── Radio.test.js
├── components // child components
│ ├── Input.js
│ ├── Input.test.js
│ ├── Label.js
│ ├── Label.test.js
│ └── index.js // the child list index (2)
├── images
│ ├── Checked.js
│ ├── Checked.test.js
│ ├── Focused.js
│ ├── Focused.test.js
│ ├── checked.svg
│ ├── focused.svg
│ ├── ...
│ └── index.js // the image list index (3)
└── index.js // the component index (1)
- The component
This is the only entry point from where the component is accesed. The default export of this file is always the component. Child components and images should be made available (only one level) as well - The child list
This has no default export, exports all child components as named exports. The purpose of this file is that we can do things likeimport { Input } from './components'
importing simgle componentsimport * as components from './components'
importing all childs at once
- The image list
This has no default export, exports all child components as named exports. The purpose of this file is that we can do things likeimport { CheckedFocus } from './images'
importing simgle imagesimport * as images from './images'
importing all images at once
This apply for filenames and variable names
- PascalCase (uppercase first letter, and every words first letter, no spacer)
- Components
- Classes
- camelCase (lowercase first letter, uppercase every words first letter, no spacer)
- instances of classes
- methods
- functions
- variables
- properties
- everything
- Imports on top
- Exports on bottom
- export default fist, followed by named exports
A good example
import React from 'react'
import { makeItBold } from 'text-library'
const awesomeShout = () => makeItBold('I am awesome')
const AwesomeComponent = props => (
<div>
{awesomeShout()}
<div>
)
export default AwesomeComponent
export {
awesomeShout
}
We are using heavy tree-shaking within the app, webpack will remove any
function and statement that is not used within another module. Therefore,
if you need to do a side effect, such as modifying the global object, the
file/module has to be added to the sideEffects
list within package.json
.
When creating a new route, consider using a lazy loaded component. You can
define any component as lazy using the Loadable
component from react-loadable
.
Example:
const MyComponent = Loadable({
loader: () => import(/* webpackChunkName: "mycomponent" */ './components/MyComponent'),
modules: ['./components/MyComponent'],
webpack: () => [require.resolveWeak('./components/MyComponent')]
})
The webpackChunkName is a human readably file name that will be generated by
webpack, there's no relation to the module itself. Every other reference in
modules
and webpack
has to be the same string as in the dynamic import()
expression.
We are using the jsnlog library for the client side logging functionality. It is already set up to send the log statements to the React WebApi endpoint (which internally uses NLog for logging purposes). It is offering out-of-the box unhandled client-side exceptions logging which is pretty neat since we don't have to do anything by ourselves. When we want to manually add some log statements, we can do it in the following way:
import { JL } from 'jsnlog'
class SomeComponentThatNeedsLogging extends PureComponent {
render () {
var logger = JL()
logger.info('hello from the logger')
return (
<div>There was a log statement executed in the component</div>
)
}
}
All the available log methods (options) are described here .
By adding the parameter webtests=true
to any URL, data-refname
attributes are generated thorough the whole markup. There are three webtest helper methods available, to conviniently deal with those.
- To select elements, an
"refSelector"
is needed (see section below, for how to get one) - All webtest functions are members of the global
__WEBTESTS__
object queryRefAll()
is for selecting an array of elementsqueryRef()
is for selecting a single element. It works the same, but it returns the first element of the array.
// select the sorting dropdown on the searchresults
__WEBTESTS__.queryRef("searchresult sortfilter select-dropdown")
// select all bookmark buttons in the searchresults
__WEBTESTS__.queryRefAll("searchresult resultlist listing listing-bookmarkbutton")
- There is a helper method in the
testExecutor
to get DOM elements by refname - For further selecting you can also specify further css selectors, to go deeper
// simple selection by refnames
var selectElement = testExecutor.GetElementByReactRef(
"searchresult sortfilter select-dropdown"
);
If you need to go deeper, add CSS selectors
// simple selection by refnames
var optionElement = testExecutor.GetElementByReactRef(
"searchresult sortfilter select-dropdown",
"option[value='2']"
);
- A
"refSelector"
is just a hierarchical space separated string of refnames (similar to a CSS slector). .getRefSelector()
is for easy generation of"refSelectors"
- TIPP: Use the "right-click -> inspect element" in chrome, and execute the function in the console
// get the refSelector of the currently selecte element while inspecting in chrome -> $0
__WEBTESTS__.getRefSelector($0)
// get the refSelector of a given DOM element (possible but not so usefull)
__WEBTESTS__.getRefSelector(document.querySelector('article'))
The (GTM) Tracker has support for webtest mode. This means that when the application runs in webtest
mode, the GTM events are not pushed to the "real" dataLayer object, but instead stored in the sessionStorage['datalayer'] key. This allows us to easily validate the triggered events in our webtests. An example of the structure looks like this:
{
"www_Search_KPI-Apply_Standard_Filter" : {
"searchGroupName" : "Flat, House",
"search_locationName" : "Düdingen",
"search_zip" : "",
"search_locationType" : "Place",
"search_locationId" : "1090",
"offerTypeName" : "Rent",
"event" : "www_Search_KPI",
"eventAction" : "Apply_Standard_Filter",
"numberOfRoomsFrom" : "4.5",
"searchResultCount" : 7
},
"www_Interaction_PI-List-Open_Filter" : {
"eventLabel" : "CTA",
"eventAction" : "Open_Filter",
"event" : "www_Interaction_PI-List"
}
}
To support this, there is an additional function inside the global __WEBTESTS__
object
validateGtmEvent(<eventId>)
which accepts an eventId (combination betweenevent
andeventAction
properties of the GTM object)
// returns true
const validationResult = __WEBTESTS__.validateGtmEvent('www_Search_KPI-Apply_Standard_Filter')
// returns false as the specified eventId is not found in the example object above
const validationResult = __WEBTESTS__.validateGtmEvent('some_non_existing_event_Id')
There is a helper method in the selenium testExecutor class S24SeleniumTestExecutor.cs -> ValidateGtmEvent()
which provides the functionality to the Webtest methods.
// Existence validation of certain gtm event on the home page
public HomePageReactObject ValidateGtmEvent(string eventIdentifier)
{
var validationResult = testExecutor.ValidateGtmEvent(eventIdentifier);
Assert.IsTrue(validationResult, $"Gtm object with id {eventIdentifier} was not found");
return this;
}
There is now an experimental Dockerfile included for playing around with. The ultimate aim of this file is to allow for pinning of the exact commonly agreed upon versions of Node and NPM. The performance in developer local use most likely depends a lot on the machine local setup of the backing Docker daemon / VM.
The build relies on the .npmrc-docker
file added into this repository pulling
the registry token for our private registry out of the environment variable
NPM_TOKEN
passed in via a build time ARG
in the Dockerfile
. Setting the
variable up is your own responsibility; there is no automation in place to do
that for you.
- Cmd.exe
docker build --build-arg NPM_TOKEN=%NPM_TOKEN% -t react-frontend .
- Powershell
docker build --build-arg NPM_TOKEN=$Env.NPM_TOKEN -t react-frontend .
docker build --build-arg NPM_TOKEN=$NPM_TOKEN -t react-frontend .
docker run -it react-frontend npm run test
docker run -it react-frontend npm run lint