Repository: learningequality/kolibri

Issue:
$computedClass warning might be too sensitive
### Observed behavior

We added a warning:

> Used the $computedClass method for a class definition without any pseudo selectors ...  Please use a v-bind:style directive instead.

However I think there might be some cases where it's necessary to use this even without pseudo-selectors:

* I've been having a bit of trouble with CSS specificity rules, which are different depending on whether the style is defined using `:style` vs `:class`. It has been a bit challenging to get the two to have the right precedence related to each other. (Side-note - I think source maps would have helped here)
* There might be situations where a class is necessary in non-pseudo-selector cases - for example when defining the classes in vue transitions.



### Expected behavior

we should investigate these cases and decide whether the warning is appropriate, or if there was another way of implementing

### User-facing consequences

N/A

### Errors and logs

![image](https://user-images.githubusercontent.com/2367265/51809236-95a8ac00-2253-11e9-9a29-225b3b7d0123.png)


### Context

0.12


Discussion:

Resolving patch:
diff --git a/kolibri/core/hooks.py b/kolibri/core/hooks.py
--- a/kolibri/core/hooks.py
+++ b/kolibri/core/hooks.py
@@ -15,7 +15,9 @@
 from __future__ import unicode_literals
 
 import logging
+import warnings
 
+from kolibri.plugins.hooks import abstract_method
 from kolibri.plugins.hooks import KolibriHook
 from kolibri.plugins.utils import plugin_url
 
@@ -62,3 +64,47 @@ def plugin_url(self, plugin_class, url_name):
     class Meta:
 
         abstract = True
+
+
+class MultipleThemesWarning(UserWarning):
+    pass
+
+
+class ThemeHook(KolibriHook):
+    """
+    A hook to allow custom theming of Kolibri
+    """
+
+    class Meta:
+
+        abstract = True
+
+    @property
+    @abstract_method
+    def theme(self):
+        default = {
+            # Whether to show the Kolibri log
+            # Boolean
+            "showKolibriLogo": True,
+            # URL for custom logo
+            "customLogoURL": None,
+            # URL for custom login background image
+            "splashBackgroundURL": None,
+            # Color Palette specification
+            "paletteColors": {},
+            # Brand Color specification
+            "brandColors": {},
+            # Mapping from colors to particular usage
+            "tokenMapping": {},
+        }
+        theme = {}
+        once = False
+        for hook in self.registered_hooks:
+            if once:
+                warnings.warn("Multiple themes defined by plugins, ignoring all themes")
+                return default
+            for key in default:
+                theme[key] = getattr(hook, key, theme[key])
+            once = True
+
+        return theme or default
diff --git a/kolibri/core/templatetags/kolibri_tags.py b/kolibri/core/templatetags/kolibri_tags.py
--- a/kolibri/core/templatetags/kolibri_tags.py
+++ b/kolibri/core/templatetags/kolibri_tags.py
@@ -31,6 +31,7 @@
 import kolibri
 from kolibri.core.device.models import ContentCacheKey
 from kolibri.core.hooks import NavigationHook
+from kolibri.core.hooks import ThemeHook
 from kolibri.core.webpack.utils import webpack_asset_render
 from kolibri.utils import conf
 from kolibri.utils import i18n
@@ -121,6 +122,20 @@ def kolibri_navigation_actions():
     return webpack_asset_render(NavigationHook)
 
 
+@register.simple_tag()
+def kolibri_theme():
+    """
+    A tag to include a theme configuration object to add custom theming to Kolibri.
+    :return: An html string
+    """
+    template = """
+    <script>
+      var customTheme = JSON.parse('{theme}');
+    </script>
+    """
+    return mark_safe(template.format(theme=json.dumps(ThemeHook().theme)))
+
+
 @register.simple_tag(takes_context=True)
 def kolibri_set_urls(context):
     # Modified from:


Test patch:
diff --git a/kolibri/core/assets/test/utils/colour.spec.js b/kolibri/core/assets/test/utils/colour.spec.js
deleted file mode 100644
--- a/kolibri/core/assets/test/utils/colour.spec.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { lighten, darken } from '../../src/utils/colour';
-
-describe('colour utils', () => {
-  describe('lighten function', () => {
-    it('should lighten a colour using a ratio', () => {
-      expect(lighten('#555555', 0.1)).toEqual('rgb(94, 94, 94)');
-    });
-    it('should lighten a colour using a percentage', () => {
-      expect(lighten('#555555', '10%')).toEqual('rgb(94, 94, 94)');
-    });
-  });
-  describe('darken function', () => {
-    it('should darken a colour using a ratio', () => {
-      expect(darken('#555555', 0.1)).toEqual('rgb(77, 77, 77)');
-    });
-    it('should darken a colour using a percentage', () => {
-      expect(darken('#555555', '10%')).toEqual('rgb(77, 77, 77)');
-    });
-  });
-});
diff --git a/kolibri/core/assets/test/views/elapsed-time.spec.js b/kolibri/core/assets/test/views/elapsed-time.spec.js
--- a/kolibri/core/assets/test/views/elapsed-time.spec.js
+++ b/kolibri/core/assets/test/views/elapsed-time.spec.js
@@ -8,10 +8,7 @@ localVue.use(Vuex);
 const DUMMY_CURRENT_DATE = new Date(2017, 0, 1, 1, 1, 1);
 
 function makeWrapper(options) {
-  const getters = {
-    $coreGrey300: () => 'gray',
-  };
-  const store = new Vuex.Store({ getters });
+  const store = new Vuex.Store();
   return mount(ElapsedTime, {
     ...options,
     store,
diff --git a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsExerciseLearners.spec.js.snap b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsExerciseLearners.spec.js.snap
--- a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsExerciseLearners.spec.js.snap
+++ b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsExerciseLearners.spec.js.snap
@@ -3,7 +3,7 @@
 exports[`ReportsExerciseLearners doesn't render groups information when show groups set to false 1`] = `
 <div class="core-table-container">
   <table class="core-table">
-    <thead style="border-bottom: 1px solid #e0e0e0; font-size: 12px; color: rgb(97, 97, 97);">
+    <thead style="border-bottom: 1px solid #eeeeee; font-size: 12px; color: rgb(97, 97, 97);">
       <tr>
         <th>Name</th>
         <th>Progress</th>
@@ -28,7 +28,7 @@ exports[`ReportsExerciseLearners doesn't render groups information when show gro
     <div>learner2</div>
   </td>
   <td>
-    <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+    <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     Started
   </div></span>
       <!---->
@@ -46,7 +46,7 @@ exports[`ReportsExerciseLearners doesn't render groups information when show gro
 exports[`ReportsExerciseLearners renders all entries 1`] = `
 <div class="core-table-container">
   <table class="core-table">
-    <thead style="border-bottom: 1px solid #e0e0e0; font-size: 12px; color: rgb(97, 97, 97);">
+    <thead style="border-bottom: 1px solid #eeeeee; font-size: 12px; color: rgb(97, 97, 97);">
       <tr>
         <th>Name</th>
         <th>Progress</th>
@@ -77,7 +77,7 @@ exports[`ReportsExerciseLearners renders all entries 1`] = `
     <div>learner2</div>
   </td>
   <td>
-    <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+    <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     Started
   </div></span>
       <!---->
diff --git a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonExerciseLearnerListPage.spec.js.snap b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonExerciseLearnerListPage.spec.js.snap
--- a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonExerciseLearnerListPage.spec.js.snap
+++ b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonExerciseLearnerListPage.spec.js.snap
@@ -17,12 +17,12 @@ exports[`ReportsLessonExerciseLearnerListPage for a lesson restricted to some gr
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #df4d4f;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #c62828;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
@@ -52,7 +52,7 @@ exports[`ReportsLessonExerciseLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     1 started
   </div></span>
     <!---->
@@ -69,12 +69,12 @@ exports[`ReportsLessonExerciseLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     1
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #df4d4f;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #c62828;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
@@ -94,12 +94,12 @@ exports[`ReportsLessonExerciseLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #df4d4f;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some needing help" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="error" category="alert" style="fill: #c62828;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
diff --git a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonResourceLearnerListPage.spec.js.snap b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonResourceLearnerListPage.spec.js.snap
--- a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonResourceLearnerListPage.spec.js.snap
+++ b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsLessonResourceLearnerListPage.spec.js.snap
@@ -28,7 +28,7 @@ exports[`ReportsLessonResourceLearnerListPage for a lesson restricted to some gr
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
@@ -72,7 +72,7 @@ exports[`ReportsLessonResourceLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     1 started
   </div></span>
     <!---->
@@ -100,7 +100,7 @@ exports[`ReportsLessonResourceLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     1
   </div></span>
     <!---->
@@ -121,7 +121,7 @@ exports[`ReportsLessonResourceLearnerListPage for an entire class lesson when di
   </div></span>
     <!---->
   </div>
-  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+  <div debug="ratio; has some started" class="item"><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     0
   </div></span>
     <!---->
diff --git a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsResourceLearners.spec.js.snap b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsResourceLearners.spec.js.snap
--- a/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsResourceLearners.spec.js.snap
+++ b/kolibri/plugins/coach/assets/test/views/reports/__snapshots__/ReportsResourceLearners.spec.js.snap
@@ -3,7 +3,7 @@
 exports[`ReportsResourceLearners doesn't render groups information when show groups set to false 1`] = `
 <div class="core-table-container">
   <table class="core-table">
-    <thead style="border-bottom: 1px solid #e0e0e0; font-size: 12px; color: rgb(97, 97, 97);">
+    <thead style="border-bottom: 1px solid #eeeeee; font-size: 12px; color: rgb(97, 97, 97);">
       <tr>
         <th>Name</th>
         <th>Status</th>
@@ -11,7 +11,7 @@ exports[`ReportsResourceLearners doesn't render groups information when show gro
         <!---->
         <th>Last activity</th>
       </tr>
-    </thead><span tag="tbody" name="list"><tr><td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #3a3a3a;"></mat-svg></div> <div dir="auto" class="label">
+    </thead><span tag="tbody" name="list"><tr><td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #212121;"></mat-svg></div> <div dir="auto" class="label">
           learner1
         </div></span></td>
     <td>
@@ -28,11 +28,11 @@ exports[`ReportsResourceLearners doesn't render groups information when show gro
 </span></td>
     </tr>
     <tr>
-      <td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #3a3a3a;"></mat-svg></div> <div dir="auto" class="label">
+      <td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #212121;"></mat-svg></div> <div dir="auto" class="label">
           learner2
         </div></span></td>
       <td>
-        <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+        <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     Started
   </div></span>
           <!---->
@@ -52,7 +52,7 @@ exports[`ReportsResourceLearners doesn't render groups information when show gro
 exports[`ReportsResourceLearners renders all entries 1`] = `
 <div class="core-table-container">
   <table class="core-table">
-    <thead style="border-bottom: 1px solid #e0e0e0; font-size: 12px; color: rgb(97, 97, 97);">
+    <thead style="border-bottom: 1px solid #eeeeee; font-size: 12px; color: rgb(97, 97, 97);">
       <tr>
         <th>Name</th>
         <th>Status</th>
@@ -62,7 +62,7 @@ exports[`ReportsResourceLearners renders all entries 1`] = `
         </th>
         <th>Last activity</th>
       </tr>
-    </thead><span tag="tbody" name="list"><tr><td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #3a3a3a;"></mat-svg></div> <div dir="auto" class="label">
+    </thead><span tag="tbody" name="list"><tr><td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #212121;"></mat-svg></div> <div dir="auto" class="label">
           learner1
         </div></span></td>
     <td>
@@ -83,11 +83,11 @@ exports[`ReportsResourceLearners renders all entries 1`] = `
 </span></td>
     </tr>
     <tr>
-      <td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #3a3a3a;"></mat-svg></div> <div dir="auto" class="label">
+      <td><span class="labeled-icon-wrapper"><div class="icon"><mat-svg name="person" category="social" style="fill: #212121;"></mat-svg></div> <div dir="auto" class="label">
           learner2
         </div></span></td>
       <td>
-        <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #2196f3;"></mat-svg></div> <div dir="auto" class="label">
+        <div><span class="labeled-icon-wrapper" nowrap="nowrap"><div class="icon"><mat-svg name="access_time" category="device" style="fill: #03a9f4;"></mat-svg></div> <div dir="auto" class="label">
     Started
   </div></span>
           <!---->
diff --git a/kolibri/plugins/user/assets/test/views/profile-page.spec.js b/kolibri/plugins/user/assets/test/views/profile-page.spec.js
--- a/kolibri/plugins/user/assets/test/views/profile-page.spec.js
+++ b/kolibri/plugins/user/assets/test/views/profile-page.spec.js
@@ -3,7 +3,6 @@ import ProfilePage from '../../src/views/ProfilePage';
 import makeStore from '../makeStore';
 
 ProfilePage.methods.fetchPoints = () => {};
-ProfilePage.mixins = [];
 
 function makeWrapper() {
   const store = makeStore();