Skip to content

Instantly share code, notes, and snippets.

@artursmirnov
Last active June 22, 2016 09:13
Show Gist options
  • Save artursmirnov/8bcdcf5c9ffee0916d060f96ef2c5a8b to your computer and use it in GitHub Desktop.
Save artursmirnov/8bcdcf5c9ffee0916d060f96ef2c5a8b to your computer and use it in GitHub Desktop.
ember-app-collaboration

Ember application

This README outlines the details of collaborating on this Ember application.

Prerequisites

You will need the following things properly installed on your computer.

Please make sure you are familiar with EmberJS framework, Ember CLI tool, Mocha and Chai testing frameworks and Openlayers library. If not, please read documentation first:

Also you may use API documentation for development:

Development flow

  • Development flow is based on GitFlow
  • Feature branches must be prefixed by feature/ and forked from develop branch only
  • Hotfix branches must be prefixed by hotfix/ and forked from master branch only
  • Bugfix branches must be prefixed by bugfix/ and forked from develop branch only
  • Direct pushing to master and develop branches is strictly prohibited!
  • All branches must pass code review and CI runs before merging.
  • Merging to master and develop branches without code review is strictly prohibited! Everything merged to these two branches must be reviewed first.

Code review rules and merge requirements

  • Following the GitFlow development guidelines, any pull-request needs to be reviewed before merge.
  • Code review consists of several parts:
    • Review code changes
    • Checkout the branch and run it locally
    • Manually test changed parts of application, especially the bottlenecks you have noticed reviewing the code
    • Make sure there are no deprecation messages, warnings or errors in javascript console
    • Run tests locally (ember test) and make sure they all pass
    • Make sure there were no warnings during build process (scss-lint does not break tests but flushes warnings in build process output)
  • Code requirements to pass code review:
    • No logging/debugging logic or helpers left
    • Branch has any meaningful tests (the coverage volume is for reviewer judgement)
    • The code follows all styleguides, conventions and rules described in this document
  • Build must pass all tests and checks
  • Build must not have any regressions (exceptions might be discussed)
  • Pull request description must provide all meaningful details for reviewer and have Jira issue attached (if exists)

Installation

  • clone git repository: git clone <repository-url>
  • change into the new directory: cd <directory-name>
  • install NPM dependencies: npm install
  • install bower dependencies: bower install
  • for Windows users it may be useful to go through this giude
  • install SASS: gem install sass
  • install scss-lint: gem install scss-lint

Running / Development

Code Generators

Make use of the many generators for code, try ember help generate for more details. Generators also produce basic tests, so it is the preferred way of creating new entities.

Writing tests

First of all please go through Ember testing guide to become familiar with the concept.

We use mocha instead of qUnit as our test framework. Please take a brief view to get a general idea and understanding of BDD approach in testing. Mocha is integrated to Ember app via ember-mocha. It provides Ember test helpers mapping for mocha.

To come along with BDD approach we also use expect as an assertion library.

Basic Rules:

  • Write all types of tests:
    • Unit tests for entities
    • Integration tests for entities' visual representation and intercommunications
    • Acceptance tests for flows and behaviors
  • Reflect all changes in existing tests
  • Use ember-mocha helpers for unit tests
  • Try to stick with thin test cases. Don't make a lot of assertions in a single test. More small tests is much easier to maintain.
  • Always put test type to top-level describe, followed by -: Unit - Vessel model, Acceptance - Vessel selection and so on.
  • Acceptance tests:
    • Use nested describes to keep the lower level have just few assertions (only one ideally)
    • Make sure of the logical test case output. Mocha concatenates all nested describe titles for each test case. It should be something like: 'Users page has list of all users rendered properly' built from: describe('Users page') -> describe('has list') -> describe('of all users') -> describe('rendered properly')
  • Always use mocks and stubs in all types of tests. Test should never interact with remote APIs.

Running Tests

  • ember test - simply runs all tests
  • ember test --server - starts a CI server which track files for changes and automatically reruns tests on changes
  • ember server --environment=test - runs development server within test environment. You can open application by visiting http://localhost:4200 and interact with it. Also you can run tests at http://localhost:4200/tests route.

Building

  • ember build (development)
  • ember build --environment production (production)

Openlayers

The major part of the application is built around Openlayers library. The library itself is very feature-rich and therefore is very big in terms of compiled code size. Because of that we use custom build of Openlayers to optimize application size. The build process is completely transparent and built-in to Ember CLI. Openlayers custom builds are managed by ember-cli-openlayers-builder addon. Please take a brief view on how it works.

If you need to add a new Openlayers module to the app:

  • add the module to exports array of .ol-build config
  • restart ember server to apply changes
  • Ember CLI will automatically rebuild Openlayers according to new config and append it to vendor scripts

Please note that openlayers doesn't support ES2015 modules for dependencies management. So it is extracted to the global scope (window) and available everywhere via ol.* namespace.

However, there are some usage conventions:

  • all Openlayers functionality should be proxied via appName/helpers/ol/* helpers.
  • ol.* API should not be used anywhere in the app outside of appName/helpers/ol* directly. If you need any Openlayers function, find an appropriate helper, extend it if necessary or create one if it does not exist yet.
  • any ol helper must be extended from appName/helpers/ol/core/object which is responsible for proper Openlayers instance handling and contains some useful hooks.
  • any ol helper must implement init() hook which should create a corresponding openlayers object and pass it to the this._super() method.
  • since appName/helpers/ol/core/object extends Ember.Object and all its APIs are available for any ol helper (including Ember.Observable), it should be used the same way like any other Ember object. Don't hesitate to use get, set, computed properties and observers on ol helpers.

Deploying

Specify what it takes to deploy your app.

Further Reading / Useful Links

Ember

Ember Inspector

Other docs

Addons

Conventions and styleguides

  • Ember official recommended styleguide
  • SCSS code style: see .scss-lint.yml
  • Private variables must be prepended by _: let _privateVar
  • Variables that contain links to jQuery objects must be prepended by $: let $element
  • Commented-out code should never pass to the repository. It greatly reduces readability. Use VCS to restore deleted code.
  • Always use single quote marks for strings. This allows to avoid escaping double quote marks inside.

Internalization

We use ember-i18n for application localization.

Translation files are located at app/locales/**. Default locale is currently en-gb.

Rules

  • Developer should never put strings, which would be displayed to end user, directly. We must use translation helpers for this.
  • Use dates only with moment helpers or service since they have to be localized as well.
  • Direct strings and dates are allowed only for logging and debugging purposes
  • Any new string should be defined at least for default translation
  • To define new string add it to translations.js file as a value and choose a valid logical path as a key. Make sure it is unique.
  • Sort path keys in alphabetical order for easier navigation
  • After the string is defined, it could be used via i18n service or helpers identified by its path.
  • i18n service is injected to all types of Ember entities by default. It can be used (this.get('i18n')) everywhere out of the box
  • If you faced incorrect localization (e.g. invalid pluralization form), please refer to library documentation to solve an issue.

Styles

We use SCSS to write styles and scss-lint for codestyle validation. Please take a look at .scss-lint.yml to be aware of SCSS codestyle rules.

Structure

  • All styles are stored under appName/styles folder.
  • Main styles file is appName/styles/app.scss, it must include all other styles (using nesting).
  • All styles are splitted into strictly structured directories tree.
  • Each subdirectory must contain index.scss file wich includes all files within the folder and index.scss files from all subfolders.
  • All files with style rules must be sass partials and must be prepended by _. In this case the index.scss file will always be the last file in the folder.
  • All css animations must be stored under appName/styles/animations folder, one animation per file, and the file name must reflect the animation name.
  • All custom fonts must be defined under appName/styles/fonts folder, one font per file, and the file name must reflect the font name.
  • appName/styles/helpers/* is for helper style rules. Such rules are universal and may be used all over the application. _icons.scss contains symbol icons, _layout.scss contains layout helpers (alignment, positioning, etc.).
  • appName/styles/images/* is for base64 encoded images, one image per file. Base64 string must be assigned to a variable and file name should be equal to variable name.
  • appName/styles/mixins/* is for mixins, one mixin per file, file name should be equal to mixin name.
  • appName/styles/variables/* is for variables. Subfolders define styles of different categories.
  • appName/styles/templates/* is for application styles.

Blocks concept

  • All styles in the application are splitted into small blocks.
  • Each block is stored in its own file.
  • Each single block represents Ember template (and therefore appName/styles/templates hierarchy follows the appName/templates one).
  • Block name is always equal to corresponding template name.
  • Basicaly, the concept is: each template file has a corresponding styles, scoped into that template.
  • How the scoping works read in "Naming" section.

Naming

  • We follow the simplified BEM convention for styles naming.
  • Each template block is the "Block" in BEM notation.
  • Each template can have only one Block
  • Block class name should correspond template name.
  • Block may have as many elements as needed.
  • Elements can not have nested elements.
  • This is how scoping works: Block class name equals to it's template name (one-to-one), which means it's unique for the application. Therefore all its elements are also unique.
  • This allows to completely avoid styles cascading without collisions.

Theming

  • Theming is made by overwriting default styles for each block by theme specific styles.
  • When theme is applied, its name is applied to the application container element as a class name.
  • In order to overwrite styles for any element just nest it under .<theme-name> class.
  • Only the styles that differs from default ones have to be overwritten.
  • All theme-related styles for each Block should be stored in a separate file, e.g. appName/styles/tempaltes/<template-path>/<template-name>/_<theme-name>.scss
  • Theme-related variables should also be stored in separate files like appName/styles/variables/<category>/_<theme-name>.scss
  • Theme-related variables should have the same name as default ones with --<theme-name> postfix, e.g. $text-color -> $text-color--light.
  • Theme files should be included in index.scss files after _default.scss in order to properly overwrite default styles.
  • Styles for mobile devices are implemented as a theme called mobile.

Example

appName/templates/application.hbs

<div class="application">
  {{some-component}}

  {{outlet}}
</div>

appName/templates/map.hbs

<div class="map"></div>

appName/templates/components/some-component.hbs

<div class="some-component__title {{if isActive 'some-component__title--active'}}">
  <i class="some-component__icon icon--map"></i> {{title}}
</div>

appName/components/some-component.js

import Ember from 'ember';
export default Ember.Component.extend({
  classNames: ['some-component'] // this will apply Block root class to component root element
});

appName/styles/templates/application/_default.scss

.application {
  width: 1000px;
  padding: 20px;
  background-color: #555555;
  position: relative;
}

appName/styles/templates/application/_light.scss

.light {

  .application {
    background-color: #eeeeee;
  }

}

appName/styles/templates/application/index.scss

@import 'default';
@import 'light';

appName/styles/templates/map/_default.scss

.map {
  position: absolute;
  top: 0;
  bottom: 0;
}

appName/styles/templates/map/index.scss

@import 'default';

appName/styles/templates/components/some-component/_default.scss

.some-component {
  display: inline-block;
  padding: 5px;
  background: #999999;

  &:hover {
    color: #222222;
  }

  &::after {
    content: '!';
  }
}

.some-component__title {
  text-decoration: underline;
}

.some-component__title--active {
  font-weight: bold;
}

.some-component__icon {
  text-decoration: none;
  display: inline-block;
}

appName/styles/templates/components/some-component/_light.scss

.light {

  .some-component {
    background: #dddddd;

    &:hover {
      color: #eeeeee;
    }
  }

}

appName/styles/templates/components/some-component/index.scss

@import 'default';
@import 'light';

Restrictions

  • app.scss and all */index.scss files must be used for importing nested files only. They must not contain any style rules.
  • All style rules must be classes. Identifiers in selectors are prohibited. Identifiers should only be used for javascript needs.
  • Tag names should never be used in selectors. If you need to add style for element, always make a class for it.
  • Cascading the rules is a considered bad practice. So it is allowed only in few cases: to apply a theme, for pseudo-classes and psuedo-elements
  • Styles cascading (and nesting therefore) is allowed only for two levels deep. But it's always better to avoid it when possible.
  • !important instruction is completely prohibited since it makes styles not reusable.

CSS Images

  • All CSS images should be stored under appName/styles/images/*, one image per file.
  • CSS image is a base64-encoded string that is assigned to a SASS variable: $my-image: 'data:image/png;base64,...';
  • File name should be the same as the variable name, prefixed with _: _my-image.scss -> $my-image: '...';
  • If the image may be used in markup and/or JS, it should be assigned to a CSS class as a background image. CSS class name should be the same as the variable name, prepended with img--: $my-image -> .img--my-image: {}. width and height have to be declared in this case.
# Rules explanation: https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md
linters:
BangFormat:
enabled: true
space_before_bang: true
space_after_bang: false
BemDepth:
enabled: true
max_elements: 1
BorderZero:
enabled: true
convention: zero # or `none`
ChainedClasses:
enabled: true
ColorKeyword:
enabled: true
ColorVariable:
enabled: false
Comment:
enabled: true
style: silent
DebugStatement:
enabled: true
DeclarationOrder:
enabled: true
DisableLinterReason:
enabled: true
DuplicateProperty:
enabled: false
ElsePlacement:
enabled: true
style: same_line # or 'new_line'
EmptyLineBetweenBlocks:
enabled: true
ignore_single_line_blocks: false
EmptyRule:
enabled: true
ExtendDirective:
enabled: false
FinalNewline:
enabled: true
present: true
HexLength:
enabled: true
style: long
HexNotation:
enabled: true
style: lowercase # or 'uppercase'
HexValidation:
enabled: true
IdSelector:
enabled: true
ImportantRule:
enabled: true
ImportPath:
enabled: true
leading_underscore: false
filename_extension: false
Indentation:
enabled: true
allow_non_nested_indentation: true
character: space # or 'tab'
width: 2
LeadingZero:
enabled: true
style: include_zero # or 'exclude_zero'
MergeableSelector:
enabled: true
force_nesting: true
NameFormat:
enabled: true
allow_leading_underscore: false
convention: hyphenated_lowercase # or 'camel_case', or 'snake_case', or a regex pattern
NestingDepth:
enabled: true
max_depth: 2
ignore_parent_selectors: true
PlaceholderInExtend:
enabled: false
PropertyCount:
enabled: false
include_nested: false
max_properties: 10
PropertySortOrder:
enabled: false
ignore_unspecified: false
min_properties: 2
separate_groups: false
PropertySpelling:
enabled: true
extra_properties: []
disabled_properties: []
PropertyUnits:
enabled: true
global: [
'ch', 'em', 'ex', 'rem', # Font-relative lengths
'cm', 'in', 'mm', 'pc', 'pt', 'px', 'q', # Absolute lengths
'vh', 'vw', 'vmin', 'vmax', # Viewport-percentage lengths
'deg', 'grad', 'rad', 'turn', # Angle
'ms', 's', # Duration
'Hz', 'kHz', # Frequency
'dpi', 'dpcm', 'dppx', # Resolution
'%'] # Other
properties: {}
PseudoElement:
enabled: true
QualifyingElement:
enabled: true
allow_element_with_attribute: false
allow_element_with_class: false
allow_element_with_id: false
SelectorDepth:
enabled: true
max_depth: 2
SelectorFormat:
enabled: true
convention: hyphenated_BEM # or 'strict_BEM', or 'hyphenated_lowercase', or 'snake_case', or 'camel_case', or a regex pattern
Shorthand:
enabled: false
allowed_shorthands: [1, 2, 3]
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: false
SingleLinePerSelector:
enabled: true
SpaceAfterComma:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space'
SpaceAfterPropertyColon:
enabled: true
style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
SpaceAfterPropertyName:
enabled: true
SpaceAfterVariableName:
enabled: true
SpaceAroundOperator:
enabled: true
style: one_space # or 'at_least_one_space', or 'no_space'
SpaceBeforeBrace:
enabled: true
style: space # or 'new_line'
allow_single_line_padding: false
SpaceBetweenParens:
enabled: true
spaces: 0
StringQuotes:
enabled: true
style: single_quotes # or double_quotes
TrailingSemicolon:
enabled: true
TrailingWhitespace:
enabled: true
TrailingZero:
enabled: true
TransitionAll:
enabled: true
UnnecessaryMantissa:
enabled: true
UnnecessaryParentReference:
enabled: true
UrlFormat:
enabled: true
UrlQuotes:
enabled: true
VariableForProperty:
enabled: false
VendorPrefix:
enabled: true
identifier_list: base
additional_identifiers: []
excluded_identifiers: []
ZeroUnit:
enabled: true
Compass::*:
enabled: false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment