Skip to content

Latest commit

 

History

History
451 lines (346 loc) · 12.8 KB

CONTRIBUTING.adoc

File metadata and controls

451 lines (346 loc) · 12.8 KB
Note
I’m currently going through the code base to refactor it, modifying this along the way. Guidelines defined here are temporary and subject to changes

The style guide is based on vuejs', and rules defined there are to be followed except when superseded by rules defined here.

Components

File names

All components filenames should reflect the component’s name, and be in PascalCase.

Ordering of words in the name should be in generic-to-specific order: ButtonNormal instead of NormalButton. This greatly improves file sorting.

Additionally, prefix component names with the following:

Prefix Situation

Base

Components that are only used in other components

The

Components that are only used once in pages

Input

UI Component that are made for user data input

Inheritance

Components that…​ Should inherit from…​

are cards

BaseCard

are user input fields

BaseInput

are modals interacting with resources (add/edit/delete)

BaseModalObject

are modals

BaseModal

Tests

All components should be tested using a single test file at tests/components/ComponentName.test.js

Use the following boilerplate for the tests:

import { bootstrapComponentTest } from '../utils'
import ComponentName from '~/components/ComponentName.vue'

const { mnt, store } = bootstrapComponentTest(
  ComponentName
  // Put the default store here
  // Put the default propsData here
)

describe('<ComponentName>', () => {
  it('does what it is supposed to do', () => {
    const ComponentName = mnt(/* Provide propsData if needed */)
    // Do your actions (emit a click, etc)
    expect(/* predicate */)./* assertion */
  })
})

vuex store

Modules

Split your vuex code into modules: one per 'resource'. This should roughly match the API’s different URLS, eg. api.schoolsyst.com/homework should be handled by store/homework.js.

State

Include one empty array with the resource’s plural name as the key, one boolean value set to false named loaded, and additionnal constants that may be reused in the code base (eg. possible values of choice-restricted API fields.)

Mutations

Construct your mutations using store/index.js's getMutations.

Actions

For each mutation, when the API request fails, handle the error by showing the user a $toast.error.

Each mutation should return…​

When the action succeeded

[true, object, null] with object the value returned by the matching mutation, or the inner action’s object return value.

When the action fails because of a validation error

[false, validation, 'validation'] with validation the value returned by the validate getter, or by the inner action’s validation return value

When the action fails because of a network error

[false, error, 'network'] with error the error object from the try/catch block.

More generally, the return value is an array with:

  1. whether the action succeeded or not

  2. the object/array that results from the underlying mutation

  3. the failure level, if any. If it succeeded, this is null.

Include the following actions:

load({ force: false, verbose: true })
  • If loaded is false or force is true

    • Do a GET request

    • Use the SET mutation to store the response in the store

    • Call the POSTLOAD mutation (which sets loaded to true.)

    • If verbose is true

      • Show a $toast.success with an appropriate message

post({ data, force: false, verbose: true })
  • If force is false

    • Validate data with the validate getter

    • Let validated be the boolean result of that validation

  • If validated is true or force is true

    • Do a POST request with data

    • get the UUID from the response

    • Do a GET request with that UUID

    • Call the ADD mutation with that response

    • If verbose is true

      • Show the user a $toast.success with an appropriate message

  • Else

    • Show the user a $toast with an appropriate error message

    • Return false and the validation object

patch({ pk, data, force: false, verbose: true })
  • Let hydrated be the object with data applied

  • If force is false

    • Validate hydrated with the validate getter

    • Let validated be the boolean result of that validation

  • If validated is true or force is true

    • Do a PATCH request with data

    • get the UUID from the response

    • Do a GET request with that UUID

    • Call the CHANGE mutation with that response

    • If verbose is true

      • Show the user a $toast.success with an appropriate message

  • Else

    • Show the user a $toast with an appropriate error message

    • Return false and the validation object

remove({ pk, force: false, verbose: true })
  • If force is false

    • Search for a resource with pk as its primary key in the store

    • If no object can be found

      • Return [false, null, 'validation']

  • Let object be the found object

  • Return [true, object, null]

Getters

Include the following getters:

one(value, { by: 'your_default_pk' })
  • Return a single object from the resources object with its by property matching value.

all()
  • Return the resource array from the state

order(objects, { by: 'your_default_sorting_method' })
  • Return the objects, sorted using the by sorting method. (each sorting method is specific to the resource)

Code clarity guidelines

In vue files' <template>

Language

pug

Indentation

2 spaces

Attribute shorthands order

In single-line component uses: Put the class shorthand .class after an attribute list and the #id shortand before:

ComponentName#id(attr="value").class.class2

In multiline uses (eg. the attributes list is split on multiple lines): Put all of the shorthands before the attribute list

component#id.class.class2(
  :is="componentName"
  :style="styles"
  :class="{clickable, multiline, thin, inline}"
  :data-variant="variant"
  type="button"
  @click="$emit('click')"
  v-tooltip="tooltipContent"
)

Attribute list order

Put attributes in the following order:

  1. Definition: is (or :is)

  2. List rendering: v-for

  3. Conditionals v-if, v-else-if, v-else, v-show, v-cloak

  4. Render modifiers: v-pre, v-once

  5. Unique attributes: ref, key (or :key) note: key should be on the same line as v-for, even when you use one attribute per line. This helps differentiate key from other props

  6. Two-way binding: v-model

  7. Other directives:

  8. Bound attributes: :attribute-name

  9. Regular attributes: attribute-name

  10. Value-less attributes: attribute-name (without ="value")

  11. Events: @event-name, v-touch

  12. Content: v-html, v-text, v-tooltip

Directive shorthands

Always use the v- shorthands:

Use…​

Instead of

:attr

v-bind:attr note: a bare v-bind is still allowed

@event

v-on:event

#name

v-slot:name

Attribute values

Only the following should be written in attribute values

  • a unique scalar value

    • for template strings, interpolated values should be variables only.

  • an object literal with ≤ 5 words:

    • a key counts as a word

    • a value counts as a word

      • the value should be a scalar or a variable

  • a unique function call

  • a unique variable

For object literals, don’t add spaces around the braces (but do it in JS code)

For multi-values attributes (eg. style or class), prefer an object literal of computed values instead of a single computed value returning the entire object:

<template>
component(
  :style="{backgroundColor, color: subjectNameColor}"
)
<script>
computed: {
  backgroundColor() {
    return this.color || 'var(--black)'
  },
  subjectNameColor() {
    return this.color ? this.textColor()(this.color) : 'var(--white)'
  },
}

Instead of:

<template>
component(:style="styles")
<script>
computed: {
  styles() {
    return {
      backgroundColor: this.color || 'var(--black)',
      color: this.color ? this.textColor()(this.color) : 'var(--white)'
    }
  }
}
An exception for conditional directives (v-if, v-else-if)

In conditonnal directives, the value can be one of the above, or a logical-operator-separated list of variables, but there must be at most two operators.

Example
template(v-if="loggedIn || passwordForgotten")

If the exact same condition is reused, make a computed property instead.

Line breaks in attribute lists

If the element contains more than two attributes:

Bad example
template(v-if="thing" my-stuff :thingie="foo" bar="baz" quux)
Good example
template(
  v-if="thing"
  :thingie="foo"
  bar="baz"
  my-stuff quux
)

In vue component file’s <script>

If your component declares props that have different purposes, separate them with a comment into three categories:

  • Data

  • Supporting data (non-essential, must declare a default)

  • Styling

Example
props: {
  // Data
  color: {
    type: String,
    default: null
  },
  name: {
    type: String,
    default: null
  },
  // Supporting data
  placeholderName: {
    type: String,
    default: 'Choisir...'
  },
  // Style
  clickable: {
    type: Boolean,
    default: false
  },
  variant: {
    type: String,
    default: 'badge'
  },
  thin: {
    type: Boolean,
    default: false
  },
  noTooltip: {
    type: Boolean,
    default: false
  }
},

In vue component files' <style>

Language

stylus

Indentation

2 spaces

Sections

Separate your styles into sections using the following comment decorations (including the surrounding blank lines):

//
// SECTION_NAME
//

Include the following sections (even if empty, except for Hack), in order:

  1. Definitions
    Includes stylus variables & functions as well as CSS variables

  2. Layout
    Includes position, display, all positioning, sizing & spacing-related properties

  3. Decoration
    _Includes all border- properties, outline & box-shadow.

  4. Colors
    Includes color, background-color, opacity.

  5. Typography
    Includes all font- properties and text-decoration.

  6. Reactions
    Includes selectors that use :hover, :focus or other interaction-related pseudo selectors or classes, as well as transition, animation and @keyframes.

  7. Hacks
    CSS Hacks / bad practice styling rules. Don’t include the section comment if this section is empty

Selector nesting

Nest selectors when…​
  • The class name is not unique to the component

  • Constructing multiple selectors with & becomes more readable than listing all selectors explicitly (should not exceed 3 indentation levels)

Selector separations

  • Put 2 empty lines between selectors that apply to a different part of the component

  • Put 1 empty lines between selectors that apply to the same part of the component

Value references

Sometimes, we’re forced to use constant values when refering to something else (eg. the padding on this component should be equal to the navbar’s height).

These kind of rules, if left undocumented, are huge sources of confusion when values referenced change.

To work around this, add a comment above or after the rule:

Reference notice comment specification
//ref: <COMPONENT_NAME> [SELECTOR] REFERENCED_PROPERTY [(EXPLANATION)]

With:

COMPONENT_NAME

The referenced component

SELECTOR

The selector (if omitted, the selector is the component’s root element)

REFERENCED_PROPERTY

The property the rule gets its value from

EXPLANATION

Explain why you must do a reference.

Commit messages

This repository follows the gitmoji commit standard. Additonal conventions are added on top of the intent-indicating emoji. Here’s how commit titles should be constructed:

EMOJI [SCOPE_PREFIX SCOPE SCOPE_SUFFIX] IMPERATIVE_SENTENCE

With:

EMOJI

The emoji used. Must follow the gitmoji standard. A second emoji can be appended to the first if it is one of the following:

  • 💩 (Writing bad code that needs to be fixed)

  • 🚧 (Work in progress)

SCOPE_PREFIX, SCOPE_SUFFIX

Indicates if the changes apply to one particular area of the codebase. Must be one of the Scope Prefix/Suffix characters

SCOPE

Indicates the file/topic affected.

IMPERATIVE_SENTENCE

A sentence at the imperative tense, indicating the change applied to the codebase.

Scope Prefix/Suffix characters

The scope prefix indicates what area of the codebase changed. Less specific than the scope itself, but useful because file names overlaps. (eg. homework is a page & vuex module)

Prefixes
=

Store (vuex)

<

Components

/

Pages

$

Plugins

%

Layouts

~

Middlewares

#

Assets

 

Other (no scope prefix)

Suffixes
>

Components

:

Everything else