diff --git a/cms/envs/test.py b/cms/envs/test.py index 8577e82a3b8b..3e71011703a7 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -279,6 +279,8 @@ MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver' MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver' +TEST_THEME = COMMON_ROOT / "test" / "test-theme" + # For consistency in user-experience, keep the value of this setting in sync with # the one in lms/envs/test.py FEATURES['ENABLE_DISCUSSION_SERVICE'] = False diff --git a/cms/static/sass/_variables.scss b/cms/static/sass/partials/_variables.scss similarity index 100% rename from cms/static/sass/_variables.scss rename to cms/static/sass/partials/_variables.scss diff --git a/common/test/test-theme/cms/static/css/.gitignore b/common/test/test-theme/cms/static/css/.gitignore new file mode 100644 index 000000000000..231886279c4e --- /dev/null +++ b/common/test/test-theme/cms/static/css/.gitignore @@ -0,0 +1 @@ +*.css \ No newline at end of file diff --git a/common/test/test-theme/cms/static/images/logo.png b/common/test/test-theme/cms/static/images/logo.png new file mode 100644 index 000000000000..5efc6b63a436 Binary files /dev/null and b/common/test/test-theme/cms/static/images/logo.png differ diff --git a/common/test/test-theme/cms/static/sass/partials/_variables.scss b/common/test/test-theme/cms/static/sass/partials/_variables.scss new file mode 100644 index 000000000000..4265a16fec0a --- /dev/null +++ b/common/test/test-theme/cms/static/sass/partials/_variables.scss @@ -0,0 +1,255 @@ +// studio - utilities - variables +// ==================== + +// Table of Contents +// * +Paths +// * +Grid +// * +Fonts +// * +Colors - Utility +// * +Colors - Primary +// * +Colors - Shadow +// * +Color - Application +// * +Timing +// * +Archetype UI +// * +Specific UI +// * +Deprecated + +$baseline: 20px; + +// +Paths +// ==================== +$static-path: '..' !default; + +// +Grid +// ==================== +$gw-column: ($baseline*3); +$gw-gutter: $baseline; +$fg-column: $gw-column; +$fg-gutter: $gw-gutter; +$fg-max-columns: 12; +$fg-max-width: 1280px; +$fg-min-width: 900px; + +// +Fonts +// ==================== +$f-sans-serif: 'Open Sans','Helvetica Neue', Helvetica, Arial, sans-serif; +$f-monospace: 'Bitstream Vera Sans Mono', Consolas, Courier, monospace; + +// +Colors - Utility +// ==================== +$transparent: rgba(0,0,0,0); // used when color value is needed for UI width/transitions but element is transparent + +// +Colors - Primary +// ==================== +$black: rgb(0,0,0); +$black-t0: rgba($black, 0.125); +$black-t1: rgba($black, 0.25); +$black-t2: rgba($black, 0.5); +$black-t3: rgba($black, 0.75); + +$white: rgb(255,255,255); +$white-t0: rgba($white, 0.125); +$white-t1: rgba($white, 0.25); +$white-t2: rgba($white, 0.5); +$white-t3: rgba($white, 0.75); + +$gray: rgb(127,127,127); +$gray-l1: tint($gray,20%); +$gray-l2: tint($gray,40%); +$gray-l3: tint($gray,60%); +$gray-l4: tint($gray,80%); +$gray-l5: tint($gray,90%); +$gray-l6: tint($gray,95%); +$gray-l7: tint($gray,99%); +$gray-d1: shade($gray,20%); +$gray-d2: shade($gray,40%); +$gray-d3: shade($gray,60%); +$gray-d4: shade($gray,80%); + +$blue: rgb(0, 159, 230); +$blue-l1: tint($blue,20%); +$blue-l2: tint($blue,40%); +$blue-l3: tint($blue,60%); +$blue-l4: tint($blue,80%); +$blue-l5: tint($blue,90%); +$blue-d1: shade($blue,20%); +$blue-d2: shade($blue,40%); +$blue-d3: shade($blue,60%); +$blue-d4: shade($blue,80%); +$blue-s1: saturate($blue,15%); +$blue-s2: saturate($blue,30%); +$blue-s3: saturate($blue,45%); +$blue-u1: desaturate($blue,15%); +$blue-u2: desaturate($blue,30%); +$blue-u3: desaturate($blue,45%); +$blue-t0: rgba($blue, 0.125); +$blue-t1: rgba($blue, 0.25); +$blue-t2: rgba($blue, 0.50); +$blue-t3: rgba($blue, 0.75); + +$pink: rgb(183, 37, 103); // #b72567; +$pink-l1: tint($pink,20%); +$pink-l2: tint($pink,40%); +$pink-l3: tint($pink,60%); +$pink-l4: tint($pink,80%); +$pink-l5: tint($pink,90%); +$pink-d1: shade($pink,20%); +$pink-d2: shade($pink,40%); +$pink-d3: shade($pink,60%); +$pink-d4: shade($pink,80%); +$pink-s1: saturate($pink,15%); +$pink-s2: saturate($pink,30%); +$pink-s3: saturate($pink,45%); +$pink-u1: desaturate($pink,15%); +$pink-u2: desaturate($pink,30%); +$pink-u3: desaturate($pink,45%); + +$red: rgb(178, 6, 16); // #b20610; +$red-l1: tint($red,20%); +$red-l2: tint($red,40%); +$red-l3: tint($red,60%); +$red-l4: tint($red,80%); +$red-l5: tint($red,90%); +$red-d1: shade($red,20%); +$red-d2: shade($red,40%); +$red-d3: shade($red,60%); +$red-d4: shade($red,80%); +$red-s1: saturate($red,15%); +$red-s2: saturate($red,30%); +$red-s3: saturate($red,45%); +$red-u1: desaturate($red,15%); +$red-u2: desaturate($red,30%); +$red-u3: desaturate($red,45%); + +$green: rgb(37, 184, 90); // #25b85a +$green-l1: tint($green,20%); +$green-l2: tint($green,40%); +$green-l3: tint($green,60%); +$green-l4: tint($green,80%); +$green-l5: tint($green,90%); +$green-d1: shade($green,20%); +$green-d2: shade($green,40%); +$green-d3: shade($green,60%); +$green-d4: shade($green,80%); +$green-s1: saturate($green,15%); +$green-s2: saturate($green,30%); +$green-s3: saturate($green,45%); +$green-u1: desaturate($green,15%); +$green-u2: desaturate($green,30%); +$green-u3: desaturate($green,45%); + +$yellow: rgb(237, 189, 60); +$yellow-l1: tint($yellow,20%); +$yellow-l2: tint($yellow,40%); +$yellow-l3: tint($yellow,60%); +$yellow-l4: tint($yellow,80%); +$yellow-l5: tint($yellow,90%); +$yellow-d1: shade($yellow,20%); +$yellow-d2: shade($yellow,40%); +$yellow-d3: shade($yellow,60%); +$yellow-d4: shade($yellow,80%); +$yellow-s1: saturate($yellow,15%); +$yellow-s2: saturate($yellow,30%); +$yellow-s3: saturate($yellow,45%); +$yellow-u1: desaturate($yellow,15%); +$yellow-u2: desaturate($yellow,30%); +$yellow-u3: desaturate($yellow,45%); + +$orange: rgb(237, 189, 60); +$orange-l1: tint($orange,20%); +$orange-l2: tint($orange,40%); +$orange-l3: tint($orange,60%); +$orange-l4: tint($orange,80%); +$orange-l5: tint($orange,90%); +$orange-d1: shade($orange,20%); +$orange-d2: shade($orange,40%); +$orange-d3: shade($orange,60%); +$orange-d4: shade($orange,80%); +$orange-s1: saturate($orange,15%); +$orange-s2: saturate($orange,30%); +$orange-s3: saturate($orange,45%); +$orange-u1: desaturate($orange,15%); +$orange-u2: desaturate($orange,30%); +$orange-u3: desaturate($orange,45%); + +// +Colors - Shadows +// ==================== +$shadow: rgba($black, 0.2); +$shadow-l1: rgba($black, 0.1); +$shadow-l2: rgba($black, 0.05); +$shadow-d1: rgba($black, 0.4); +$shadow-d2: rgba($black, 0.6); + +// +Colors - Application +// ==================== +$color-draft: $gray-l3; +$color-live: $blue; +$color-ready: $green; +$color-warning: $orange-l2; +$color-error: $red-l2; +$color-staff-only: $black; +$color-gated: $black; +$color-visibility-set: $black; + +$color-heading-base: $gray-d2; +$color-copy-base: $gray-l1; +$color-copy-emphasized: $gray-d2; + +// +Timing +// ==================== +// used for animation/transition mixin syncing +$tmg-s3: 3.0s; +$tmg-s2: 2.0s; +$tmg-s1: 1.0s; +$tmg-avg: 0.75s; +$tmg-f1: 0.50s; +$tmg-f2: 0.25s; +$tmg-f3: 0.125s; + +// +Archetype UI +// ==================== +$ui-action-primary-color: $blue-u2; +$ui-action-primary-color-focus: $blue-s1; + +$ui-link-color: $blue-u2; +$ui-link-color-focus: $blue-s1; + +// +Specific UI +// ==================== +$ui-notification-height: ($baseline*10); +$ui-update-color: $blue-l4; + +// +Deprecated +// ==================== +// do not use, future clean up will use updated styles +$baseFontColor: $gray-d2; +$lighter-base-font-color: rgb(100,100,100); +$offBlack: #3c3c3c; +$green: #108614; +$lightGrey: #edf1f5; +$mediumGrey: #b0b6c2; +$darkGrey: #8891a1; +$extraDarkGrey: #3d4043; +$paleYellow: #fffcf1; +$yellow: rgb(255, 254, 223); +$green: rgb(37, 184, 90); +$brightGreen: rgb(22, 202, 87); +$disabledGreen: rgb(124, 206, 153); +$darkGreen: rgb(52, 133, 76); + +// These colors are updated for testing purposes +$lightBluishGrey: rgb(0, 250, 0); +$lightBluishGrey2: rgb(0, 250, 0); +$error-red: rgb(253, 87, 87); + + +//carryover from LMS for xmodules +$sidebar-color: rgb(246, 246, 246); + +// type +$sans-serif: $f-sans-serif; +$body-line-height: golden-ratio(.875em, 1); + +// carried over from LMS for xmodules +$action-primary-active-bg: #1AA1DE; // $m-blue +$very-light-text: $white; diff --git a/common/test/test-theme/lms/static/css/.gitignore b/common/test/test-theme/lms/static/css/.gitignore new file mode 100644 index 000000000000..231886279c4e --- /dev/null +++ b/common/test/test-theme/lms/static/css/.gitignore @@ -0,0 +1 @@ +*.css \ No newline at end of file diff --git a/common/test/test-theme/lms/static/images/logo.png b/common/test/test-theme/lms/static/images/logo.png new file mode 100644 index 000000000000..5efc6b63a436 Binary files /dev/null and b/common/test/test-theme/lms/static/images/logo.png differ diff --git a/common/test/test-theme/lms/static/sass/partials/base/_variables.scss b/common/test/test-theme/lms/static/sass/partials/base/_variables.scss new file mode 100644 index 000000000000..cb115484cfd6 --- /dev/null +++ b/common/test/test-theme/lms/static/sass/partials/base/_variables.scss @@ -0,0 +1,8 @@ +@import 'lms/static/sass/partials/base/variables'; + +$header-bg: rgb(0,250,0); +$footer-bg: rgb(0,250,0); +$container-bg: rgb(0,250,0); +$content-wrapper-bg: rgb(0,250,0); +$serif: 'Comic Sans', 'Comic Sans MS'; +$sans-serif: 'Comic Sans', 'Comic Sans MS'; diff --git a/common/test/test-theme/lms/templates/footer.html b/common/test/test-theme/lms/templates/footer.html new file mode 100644 index 000000000000..c9e73c011764 --- /dev/null +++ b/common/test/test-theme/lms/templates/footer.html @@ -0,0 +1,10 @@ +
diff --git a/lms/envs/test.py b/lms/envs/test.py index 54411c66937e..65d0c0dee75f 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -484,6 +484,8 @@ MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver' MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver' +TEST_THEME = COMMON_ROOT / "test" / "test-theme" + # add extra template directory for test-only templates MAKO_TEMPLATES['main'].extend([ COMMON_ROOT / 'test' / 'templates', diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/partials/base/_variables.scss similarity index 100% rename from lms/static/sass/base/_variables.scss rename to lms/static/sass/partials/base/_variables.scss diff --git a/openedx/core/djangoapps/theming/tests/test_theme_styling.py b/openedx/core/djangoapps/theming/tests/test_theme_styling.py new file mode 100644 index 000000000000..1701490591c1 --- /dev/null +++ b/openedx/core/djangoapps/theming/tests/test_theme_styling.py @@ -0,0 +1,265 @@ +""" + Tests for comprehensive themes. +""" +import unittest + +from django.conf import settings +from django.test import TestCase +from django.contrib import staticfiles + +from mock import patch +from paver.easy import call_task + +from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme + +from pavelib import assets + + +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class TestComprehensiveThemeLMS(TestCase): + """ + Test html, sass and static file overrides for comprehensive themes. + """ + + def setUp(self): + """ + Clear static file finders cache and register cleanup methods. + """ + super(TestComprehensiveThemeLMS, self).setUp() + self.addCleanup(self.clean_up) + + # Clear the internal staticfiles caches, to get test isolation. + staticfiles.finders.get_finder.cache_clear() + + @classmethod + def setUpClass(cls): + """ + Enable Comprehensive theme and compile sass files. + """ + # Apply Comprehensive theme and compile sass assets. + with patch("pavelib.assets.Env.env_tokens", {'COMPREHENSIVE_THEME_DIR': settings.TEST_THEME}): + # Configure path for themes + assets.configure_paths() + compile_sass('lms') + + super(TestComprehensiveThemeLMS, cls).setUpClass() + + def clean_up(self): + """ + Disable comprehensive theme and clear changes made for comprehensive themes. + """ + patch("pavelib.assets.Env.env_tokens", {'COMPREHENSIVE_THEME_DIR': ""}) + clear_theme_sass_dirs() + + @with_comprehensive_theme(settings.TEST_THEME) + def test_green_footer(self): + """ + Test that theme footer is used instead of default footer. + """ + resp = self.client.get('/') + self.assertEqual(resp.status_code, 200) + # This string comes from header.html of test-theme + self.assertContains(resp, "This is a footer for test-theme.") + + @with_comprehensive_theme(settings.TEST_THEME) + def test_staticfiles_search_path(self): + """ + Test that static files finders are adjusted according to the applied comprehensive theme. + """ + # Test that theme Static files directory is added to the start of STATIC_FILES_DIRS list + self.assertEqual(settings.STATICFILES_DIRS[0], settings.TEST_THEME / 'lms/static') + + @with_comprehensive_theme(settings.TEST_THEME) + def test_logo_image(self): + """ + Test that theme logo is used instead of default logo. + """ + result = staticfiles.finders.find('images/logo.png') + self.assertEqual(result, settings.TEST_THEME / 'lms/static/images/logo.png') + + @with_comprehensive_theme(settings.TEST_THEME) + def test_css_files(self): + """ + Test that theme sass files are used instead of default sass files. + """ + result = staticfiles.finders.find('css/lms-main.css') + self.assertEqual(result, settings.TEST_THEME / "lms/static/css/lms-main.css") + + lms_main_css = "" + with open(result) as css_file: + lms_main_css += css_file.read() + + self.assertIn("background:#00fa00", lms_main_css) + + +@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms') +class TestComprehensiveThemeStudio(TestCase): + """ + Test html, sass and static file overrides for comprehensive themes. + """ + + def setUp(self): + """ + Clear static file finders cache and register cleanup methods. + """ + super(TestComprehensiveThemeStudio, self).setUp() + self.addCleanup(self.clean_up) + + # Clear the internal staticfiles caches, to get test isolation. + staticfiles.finders.get_finder.cache_clear() + + @classmethod + def setUpClass(cls): + """ + Enable Comprehensive theme and compile sass files. + """ + # Apply Comprehensive theme and compile sass assets. + with patch("pavelib.assets.Env.env_tokens", {'COMPREHENSIVE_THEME_DIR': settings.TEST_THEME}): + # Configure path for themes + assets.configure_paths() + compile_sass('cms') + + super(TestComprehensiveThemeStudio, cls).setUpClass() + + def clean_up(self): + """ + Disable comprehensive theme and clear changes made for comprehensive themes. + """ + patch("pavelib.assets.Env.env_tokens", {'COMPREHENSIVE_THEME_DIR': ""}) + clear_theme_sass_dirs() + + @with_comprehensive_theme(settings.TEST_THEME) + def test_theme_adjusts_staticfiles_search_path(self): + """ + Test that static files finders are adjusted according to the applied comprehensive theme. + """ + # Test that theme Static files directory is added to the start of STATIC_FILES_DIRS list + self.assertEqual(settings.STATICFILES_DIRS[0], settings.TEST_THEME / 'cms/static') + + @with_comprehensive_theme(settings.TEST_THEME) + def test_overridden_logo_image(self): + """ + Test that theme logo is used instead of default logo. + """ + result = staticfiles.finders.find('images/logo.png') + self.assertEqual(result, settings.TEST_THEME / 'cms/static/images/logo.png') + + @with_comprehensive_theme(settings.TEST_THEME) + def test_overridden_css_files(self): + """ + Test that theme sass files are used instead of default sass. + """ + result = staticfiles.finders.find('css/studio-main.css') + self.assertEqual(result, settings.TEST_THEME / "cms/static/css/studio-main.css") + + lms_main_css = "" + with open(result) as css_file: + lms_main_css += css_file.read() + + self.assertIn("background:#00fa00", lms_main_css) + + +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class TestComprehensiveThemeDisabledLMS(TestCase): + """ + Test Sass compilation order and sass overrides for comprehensive themes. + """ + + def setUp(self): + """ + Clear static file finders cache. + """ + super(TestComprehensiveThemeDisabledLMS, self).setUp() + + # Clear the internal staticfiles caches, to get test isolation. + staticfiles.finders.get_finder.cache_clear() + + @classmethod + def setUpClass(cls): + """ + Compile sass files. + """ + # compile LMS SASS + compile_sass('lms') + + super(TestComprehensiveThemeDisabledLMS, cls).setUpClass() + + def test_logo(self): + """ + Test that default logo is picked in case of no comprehensive theme. + """ + result = staticfiles.finders.find('images/logo.png') + self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/logo.png') + + def test_css(self): + """ + Test that default css files served without comprehensive themes applied. + """ + result = staticfiles.finders.find('css/lms-main.css') + self.assertEqual(result, settings.REPO_ROOT / "lms/static/css/lms-main.css") + + lms_main_css = "" + with open(result) as css_file: + lms_main_css += css_file.read() + + self.assertNotIn("background:#00fa00", lms_main_css) + + +@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms') +class TestComprehensiveThemeDisabledStudio(TestCase): + """ + Test default html, sass and static file are used when no theme is enabled. + """ + + def setUp(self): + """ + Clear static file finders cache. + """ + super(TestComprehensiveThemeDisabledStudio, self).setUp() + + # Clear the internal staticfiles caches, to get test isolation. + staticfiles.finders.get_finder.cache_clear() + + @classmethod + def setUpClass(cls): + """ + Compile sass files. + """ + # compile studio sass + compile_sass('cms') + + super(TestComprehensiveThemeDisabledStudio, cls).setUpClass() + + def test_css(self): + """ + Test that default css files served when no theme is enabled. + """ + result = staticfiles.finders.find('css/studio-main.css') + self.assertEqual(result, settings.REPO_ROOT / "cms/static/css/studio-main.css") + + lms_main_css = "" + with open(result) as css_file: + lms_main_css += css_file.read() + + self.assertNotIn("background:#00fa00", lms_main_css) + + +def clear_theme_sass_dirs(): + """ + Clear THEME dirs from SASS_DIRECTORIES and SASS_LOOKUP_DIRECTORIES so that the next sass compilation + run does not include directories from previous run. + """ + assets.SASS_DIRECTORIES["THEME_LMS"] = [] + assets.SASS_DIRECTORIES["THEME_CMS"] = [] + assets.SASS_LOOKUP_DIRECTORIES["THEME_LMS"] = [] + assets.SASS_LOOKUP_DIRECTORIES["THEME_CMS"] = [] + + +def compile_sass(system): + """ + Process xmodule assets and compile sass files for lms. + + :param system - 'lms' or 'cms', specified the system to compile sass for. + """ + # Compile system sass files + call_task('pavelib.assets.update_assets', args=(system, "--settings=test")) diff --git a/pavelib/assets.py b/pavelib/assets.py index d7c8c13c0da3..1390bc4629eb 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -20,20 +20,57 @@ ALL_SYSTEMS = ['lms', 'studio'] COFFEE_DIRS = ['lms', 'cms', 'common'] -# A list of directories. Each will be paired with a sibling /css directory. -COMMON_SASS_DIRECTORIES = [ - path("common/static/sass"), -] -LMS_SASS_DIRECTORIES = [ - path("lms/static/sass"), - path("lms/static/themed_sass"), - path("lms/static/certificates/sass"), -] -CMS_SASS_DIRECTORIES = [ - path("cms/static/sass"), -] -THEME_SASS_DIRECTORIES = [] -SASS_LOAD_PATHS = ['common/static', 'common/static/sass'] + +# Directories that contain SASS files that need to be compiled, each element in a list of paths is a tuple with the +# following format +# (sass_dir, css_dir) +# where sass_dir points to source sass files and css_dir points to destination dir where css files should be put. +SASS_DIRECTORIES = { + "COMMON": [ + (path("common/static/sass"), path("common/static/css")) + ], + "LMS": [ + (path("lms/static/sass"), path("lms/static/css")), + (path("lms/static/themed_sass"), path("lms/static/css")), + (path("lms/static/certificates/sass"), path("lms/static/certificates/css")), + ], + "CMS": [ + (path("cms/static/sass"), path("cms/static/css")), + ], + "THEME_LMS": [ + + ], + "THEME_CMS": [ + + ], +} + +# Directories that needs to be added LOOKUP Path while compiling sass, each element of the list is a path object +# containing the path to be added to lookup paths +SASS_LOOKUP_DIRECTORIES = { + "COMMON": [ + path("common/static"), + path("common/static/sass"), + ], + "LMS": [ + path("lms/static/sass/partials"), + path("lms/static/sass"), + path("lms/static/themed_sass"), + path("lms/static/certificates/sass"), + ], + "CMS": [ + path("cms/static/sass/partials"), + path("cms/static/sass"), + path("lms/static/sass/partials"), # TODO: remove usage of base/_variables.scss from + # common/static/xmodule/modules/css/_module-styles.scss and also this line. + ], + "THEME_LMS": [ + + ], + "THEME_CMS": [ + + ], +} def configure_paths(): @@ -48,7 +85,8 @@ def configure_paths(): css_dir = theme_root / "static" / "css" if sass_dir.isdir(): css_dir.mkdir_p() - THEME_SASS_DIRECTORIES.append(sass_dir) + SASS_DIRECTORIES['THEME'].append((sass_dir, css_dir)) + SASS_LOOKUP_DIRECTORIES['THEME'].append(sass_dir) if edxapp_env.env_tokens.get("COMPREHENSIVE_THEME_DIR", ""): theme_dir = path(edxapp_env.env_tokens["COMPREHENSIVE_THEME_DIR"]) @@ -56,17 +94,24 @@ def configure_paths(): lms_css = theme_dir / "lms" / "static" / "css" if lms_sass.isdir(): lms_css.mkdir_p() - THEME_SASS_DIRECTORIES.append(lms_sass) + SASS_DIRECTORIES['THEME_LMS'].append(("lms/static/sass", lms_css)) + SASS_DIRECTORIES['THEME_LMS'].append((lms_sass, lms_css)) + SASS_LOOKUP_DIRECTORIES['THEME_LMS'].append(lms_sass / "partials") + SASS_LOOKUP_DIRECTORIES['THEME_LMS'].append(lms_sass) + cms_sass = theme_dir / "cms" / "static" / "sass" cms_css = theme_dir / "cms" / "static" / "css" if cms_sass.isdir(): cms_css.mkdir_p() - THEME_SASS_DIRECTORIES.append(cms_sass) + SASS_DIRECTORIES['THEME_CMS'].append(("cms/static/sass", cms_css)) + SASS_DIRECTORIES['THEME_CMS'].append((cms_sass, cms_css)) + SASS_LOOKUP_DIRECTORIES['THEME_CMS'].append(cms_sass / "partials") + SASS_LOOKUP_DIRECTORIES['THEME_CMS'].append(cms_sass) configure_paths() -def applicable_sass_directories(systems=None): +def sass_source_directories(systems=None): """ Determine the applicable set of SASS directories to be compiled for the specified list of systems. @@ -80,15 +125,53 @@ def applicable_sass_directories(systems=None): if not systems: systems = ALL_SYSTEMS applicable_directories = [] - applicable_directories.extend(COMMON_SASS_DIRECTORIES) + + applicable_directories.extend(SASS_DIRECTORIES['COMMON']) if "lms" in systems: - applicable_directories.extend(LMS_SASS_DIRECTORIES) + # If Theme is enabled compile sass for the theme only + if SASS_DIRECTORIES['THEME_LMS']: + applicable_directories.extend(SASS_DIRECTORIES['THEME_LMS']) + # If Theme is disabled compile sass lms only + else: + applicable_directories.extend(SASS_DIRECTORIES['LMS']) if "studio" in systems or "cms" in systems: - applicable_directories.extend(CMS_SASS_DIRECTORIES) - applicable_directories.extend(THEME_SASS_DIRECTORIES) + # If Theme is enabled compile sass for the theme only + if SASS_DIRECTORIES['THEME_CMS']: + applicable_directories.extend(SASS_DIRECTORIES['THEME_CMS']) + # If Theme is disabled compile sass cms only + else: + applicable_directories.extend(SASS_DIRECTORIES['CMS']) + return applicable_directories +def sass_lookup_directories(systems=None): + """ + Determine the sass directories to be added to sass lookup paths. + + Args: + systems: A list of systems (defaults to all) + + Returns: + A list of SASS directories to be added to SASS lookup path. + """ + if not systems: + systems = ALL_SYSTEMS + system_sass_lookup_directories = [] + system_sass_lookup_directories.extend(SASS_LOOKUP_DIRECTORIES['COMMON']) + + if "lms" in systems: + # Put theme sass at the top so that theme directories have highest priority on sass file lookup. + system_sass_lookup_directories.extend(SASS_LOOKUP_DIRECTORIES['THEME_LMS']) + system_sass_lookup_directories.extend(SASS_LOOKUP_DIRECTORIES['LMS']) + if "studio" in systems or "cms" in systems: + # Put theme sass at the top so that theme directories have highest priority on sass file lookup. + system_sass_lookup_directories.extend(SASS_LOOKUP_DIRECTORIES['THEME_CMS']) + system_sass_lookup_directories.extend(SASS_LOOKUP_DIRECTORIES['CMS']) + + return system_sass_lookup_directories + + class CoffeeScriptWatcher(PatternMatchingEventHandler): """ Watches for coffeescript changes @@ -126,7 +209,7 @@ def register(self, observer): """ register files with observer """ - for dirname in SASS_LOAD_PATHS + applicable_sass_directories(): + for dirname in sass_lookup_directories(): paths = [] if '*' in dirname: paths.extend(glob.glob(dirname)) @@ -240,12 +323,12 @@ def compile_sass(options): output_style = 'compressed' timing_info = [] - system_sass_directories = applicable_sass_directories(systems) - all_sass_directories = applicable_sass_directories() + system_sass_directories = sass_source_directories(systems) + system_sass_lookup_directories = sass_lookup_directories(systems) dry_run = tasks.environment.dry_run - for sass_dir in system_sass_directories: + for system_sass_dir, system_css_dir in system_sass_directories: start = datetime.now() - css_dir = sass_dir.parent / "css" + css_dir = system_css_dir or system_sass_dir.parent / "css" if force: if dry_run: @@ -257,17 +340,17 @@ def compile_sass(options): if dry_run: tasks.environment.info("libsass {sass_dir}".format( - sass_dir=sass_dir, + sass_dir=system_sass_dir, )) else: sass.compile( - dirname=(sass_dir, css_dir), - include_paths=SASS_LOAD_PATHS + all_sass_directories, + dirname=(system_sass_dir, css_dir), + include_paths=system_sass_lookup_directories, source_comments=source_comments, output_style=output_style, ) duration = datetime.now() - start - timing_info.append((sass_dir, css_dir, duration)) + timing_info.append((system_sass_dir, css_dir, duration)) print("\t\tFinished compiling Sass:") if not dry_run: diff --git a/pavelib/paver_tests/test_assets.py b/pavelib/paver_tests/test_assets.py index b690b6d0fe85..bdfa9468b070 100644 --- a/pavelib/paver_tests/test_assets.py +++ b/pavelib/paver_tests/test_assets.py @@ -1,10 +1,17 @@ """Unit tests for the Paver asset tasks.""" import ddt +import os from paver.easy import call_task +from mock import patch +from path import Path +from pavelib import assets from .utils import PaverTestCase +ROOT_PATH = Path(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) +TEST_THEME = ROOT_PATH / "common/test/test-theme" # pylint: disable=invalid-name + @ddt.ddt class TestPaverAssetTasks(PaverTestCase): @@ -56,3 +63,99 @@ def test_compile_sass(self, options): expected_messages.append("rm -rf cms/static/css/*.css") expected_messages.append("libsass cms/static/sass") self.assertEquals(self.task_messages, expected_messages) + + +@ddt.ddt +class TestPaverThemeAssetTasks(PaverTestCase): + """ + Test the Paver asset tasks. + """ + + @classmethod + def setUpClass(cls): + with patch( + "pavelib.assets.Env.env_tokens", + {'COMPREHENSIVE_THEME_DIR': TEST_THEME} + ): + # Configure path for themes + assets.configure_paths() + super(TestPaverThemeAssetTasks, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + patch("pavelib.assets.Env.env_tokens", {'COMPREHENSIVE_THEME_DIR': ""}) + assets.SASS_DIRECTORIES["THEME_LMS"] = [] + assets.SASS_DIRECTORIES["THEME_CMS"] = [] + assets.SASS_LOOKUP_DIRECTORIES["THEME_LMS"] = [] + assets.SASS_LOOKUP_DIRECTORIES["THEME_CMS"] = [] + super(TestPaverThemeAssetTasks, cls).tearDownClass() + + @ddt.data( + [""], + ["--force"], + ["--debug"], + ["--system=lms"], + ["--system=lms --force"], + ["--system=studio"], + ["--system=studio --force"], + ["--system=lms,studio"], + ["--system=lms,studio --force"], + ) + @ddt.unpack + def test_compile_theme_sass(self, options): + """ + Test the "compile_sass" task. + """ + parameters = options.split(" ") + system = [] + + if "--system=studio" not in parameters: + system += ["lms"] + if "--system=lms" not in parameters: + system += ["studio"] + + debug = "--debug" in parameters + force = "--force" in parameters + + self.reset_task_messages() + + # compile sass files + call_task('pavelib.assets.compile_sass', options={"system": system, "debug": debug, "force": force}) + + expected_messages = [] + + if force: + expected_messages.append("rm -rf common/static/css/*.css") + expected_messages.append("libsass common/static/sass") + + if "lms" in system: + # Test that first lms sass is compiled with overrides from partial + if force: + expected_messages.append( + "rm -rf " + str(TEST_THEME) + "/lms/static/css/*.css", + ) + expected_messages.append("libsass lms/static/sass") + + # Test that after compiling lms sass theme sass is compiled that overrides existing css if any + if force: + expected_messages.append( + "rm -rf " + str(TEST_THEME) + "/lms/static/css/*.css", + ) + expected_messages.append("libsass " + str(TEST_THEME) + "/lms/static/sass") + + if "studio" in system: + # Test that first cms sass is compiled with overrides from partial + if force: + expected_messages.append( + "rm -rf " + str(TEST_THEME) + "/cms/static/css/*.css", + ) + expected_messages.append("libsass cms/static/sass") + + # Test that after compiling cms sass theme sass is compiled that overrides existing css if any + if force: + expected_messages.append( + "rm -rf " + str(TEST_THEME) + "/cms/static/css/*.css", + ) + expected_messages.append("libsass " + str(TEST_THEME) + "/cms/static/sass") + + self.assertEquals(self.task_messages, expected_messages) diff --git a/themes/red-theme/lms/static/sass/lms-main-rtl.scss b/themes/red-theme/lms/static/sass/lms-main-rtl.scss deleted file mode 100755 index 3eaad226a2b3..000000000000 --- a/themes/red-theme/lms/static/sass/lms-main-rtl.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Theming overrides for sample theme -@import 'overrides'; - -// import the rest of the application -@import 'lms/static/sass/lms-main-rtl'; diff --git a/themes/red-theme/lms/static/sass/lms-main.scss b/themes/red-theme/lms/static/sass/lms-main.scss deleted file mode 100755 index d6287e821558..000000000000 --- a/themes/red-theme/lms/static/sass/lms-main.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Theming overrides for sample theme -@import 'overrides'; - -// import the rest of the application -@import 'lms/static/sass/lms-main'; diff --git a/themes/red-theme/lms/static/sass/_overrides.scss b/themes/red-theme/lms/static/sass/partials/base/_variables.scss old mode 100755 new mode 100644 similarity index 79% rename from themes/red-theme/lms/static/sass/_overrides.scss rename to themes/red-theme/lms/static/sass/partials/base/_variables.scss index 4e5e1f2b6e31..2b7b5fcf4fbb --- a/themes/red-theme/lms/static/sass/_overrides.scss +++ b/themes/red-theme/lms/static/sass/partials/base/_variables.scss @@ -1,4 +1,5 @@ -// Theming overrides for sample theme +@import 'lms/static/sass/partials/base/variables'; + $header-bg: rgb(250,0,0); $footer-bg: rgb(250,0,0); $container-bg: rgb(250,0,0);