123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- const path = require('path');
- const fs = require('fs-plus');
- const temp = require('temp').track();
- describe('atom.themes', function() {
- beforeEach(function() {
- spyOn(atom, 'inSpecMode').andReturn(false);
- spyOn(console, 'warn');
- });
- afterEach(function() {
- waitsForPromise(() => atom.themes.deactivateThemes());
- runs(function() {
- try {
- temp.cleanupSync();
- } catch (error) {}
- });
- });
- describe('theme getters and setters', function() {
- beforeEach(function() {
- jasmine.snapshotDeprecations();
- atom.packages.loadPackages();
- });
- afterEach(() => jasmine.restoreDeprecationsSnapshot());
- describe('getLoadedThemes', () =>
- it('gets all the loaded themes', function() {
- const themes = atom.themes.getLoadedThemes();
- expect(themes.length).toBeGreaterThan(2);
- }));
- describe('getActiveThemes', () =>
- it('gets all the active themes', function() {
- waitsForPromise(() => atom.themes.activateThemes());
- runs(function() {
- const names = atom.config.get('core.themes');
- expect(names.length).toBeGreaterThan(0);
- const themes = atom.themes.getActiveThemes();
- expect(themes).toHaveLength(names.length);
- });
- }));
- });
- describe('when the core.themes config value contains invalid entry', () =>
- it('ignores theme', function() {
- atom.config.set('core.themes', [
- 'atom-light-ui',
- null,
- undefined,
- '',
- false,
- 4,
- {},
- [],
- 'atom-dark-ui'
- ]);
- expect(atom.themes.getEnabledThemeNames()).toEqual([
- 'atom-dark-ui',
- 'atom-light-ui'
- ]);
- }));
- describe('::getImportPaths()', function() {
- it('returns the theme directories before the themes are loaded', function() {
- atom.config.set('core.themes', [
- 'theme-with-index-less',
- 'atom-dark-ui',
- 'atom-light-ui'
- ]);
- const paths = atom.themes.getImportPaths();
- // syntax theme is not a dir at this time, so only two.
- expect(paths.length).toBe(2);
- expect(paths[0]).toContain('atom-light-ui');
- expect(paths[1]).toContain('atom-dark-ui');
- });
- it('ignores themes that cannot be resolved to a directory', function() {
- atom.config.set('core.themes', ['definitely-not-a-theme']);
- expect(() => atom.themes.getImportPaths()).not.toThrow();
- });
- });
- describe('when the core.themes config value changes', function() {
- it('add/removes stylesheets to reflect the new config value', function() {
- let didChangeActiveThemesHandler;
- atom.themes.onDidChangeActiveThemes(
- (didChangeActiveThemesHandler = jasmine.createSpy())
- );
- spyOn(atom.styles, 'getUserStyleSheetPath').andCallFake(() => null);
- waitsForPromise(() => atom.themes.activateThemes());
- runs(function() {
- didChangeActiveThemesHandler.reset();
- atom.config.set('core.themes', []);
- });
- waitsFor('a', () => didChangeActiveThemesHandler.callCount === 1);
- runs(function() {
- didChangeActiveThemesHandler.reset();
- expect(document.querySelectorAll('style.theme')).toHaveLength(0);
- atom.config.set('core.themes', ['atom-dark-ui']);
- });
- waitsFor('b', () => didChangeActiveThemesHandler.callCount === 1);
- runs(function() {
- didChangeActiveThemesHandler.reset();
- expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(
- 2
- );
- expect(
- document
- .querySelector('style[priority="1"]')
- .getAttribute('source-path')
- ).toMatch(/atom-dark-ui/);
- atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-ui']);
- });
- waitsFor('c', () => didChangeActiveThemesHandler.callCount === 1);
- runs(function() {
- didChangeActiveThemesHandler.reset();
- expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(
- 2
- );
- expect(
- document
- .querySelectorAll('style[priority="1"]')[0]
- .getAttribute('source-path')
- ).toMatch(/atom-dark-ui/);
- expect(
- document
- .querySelectorAll('style[priority="1"]')[1]
- .getAttribute('source-path')
- ).toMatch(/atom-light-ui/);
- atom.config.set('core.themes', []);
- });
- waitsFor(() => didChangeActiveThemesHandler.callCount === 1);
- runs(function() {
- didChangeActiveThemesHandler.reset();
- expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(
- 2
- );
- // atom-dark-ui has a directory path, the syntax one doesn't
- atom.config.set('core.themes', [
- 'theme-with-index-less',
- 'atom-dark-ui'
- ]);
- });
- waitsFor(() => didChangeActiveThemesHandler.callCount === 1);
- runs(function() {
- expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(
- 2
- );
- const importPaths = atom.themes.getImportPaths();
- expect(importPaths.length).toBe(1);
- expect(importPaths[0]).toContain('atom-dark-ui');
- });
- });
- it('adds theme-* classes to the workspace for each active theme', function() {
- atom.config.set('core.themes', ['atom-dark-ui', 'atom-dark-syntax']);
- let didChangeActiveThemesHandler;
- atom.themes.onDidChangeActiveThemes(
- (didChangeActiveThemesHandler = jasmine.createSpy())
- );
- waitsForPromise(() => atom.themes.activateThemes());
- const workspaceElement = atom.workspace.getElement();
- runs(function() {
- expect(workspaceElement).toHaveClass('theme-atom-dark-ui');
- atom.themes.onDidChangeActiveThemes(
- (didChangeActiveThemesHandler = jasmine.createSpy())
- );
- atom.config.set('core.themes', [
- 'theme-with-ui-variables',
- 'theme-with-syntax-variables'
- ]);
- });
- waitsFor(() => didChangeActiveThemesHandler.callCount > 0);
- runs(function() {
- // `theme-` twice as it prefixes the name with `theme-`
- expect(workspaceElement).toHaveClass('theme-theme-with-ui-variables');
- expect(workspaceElement).toHaveClass(
- 'theme-theme-with-syntax-variables'
- );
- expect(workspaceElement).not.toHaveClass('theme-atom-dark-ui');
- expect(workspaceElement).not.toHaveClass('theme-atom-dark-syntax');
- });
- });
- });
- describe('when a theme fails to load', () =>
- it('logs a warning', function() {
- console.warn.reset();
- atom.packages
- .activatePackage('a-theme-that-will-not-be-found')
- .then(function() {}, function() {});
- expect(console.warn.callCount).toBe(1);
- expect(console.warn.argsForCall[0][0]).toContain(
- "Could not resolve 'a-theme-that-will-not-be-found'"
- );
- }));
- describe('::requireStylesheet(path)', function() {
- beforeEach(() => jasmine.snapshotDeprecations());
- afterEach(() => jasmine.restoreDeprecationsSnapshot());
- it('synchronously loads css at the given path and installs a style tag for it in the head', function() {
- let styleElementAddedHandler;
- atom.styles.onDidAddStyleElement(
- (styleElementAddedHandler = jasmine.createSpy(
- 'styleElementAddedHandler'
- ))
- );
- const cssPath = getAbsolutePath(
- atom.project.getDirectories()[0],
- 'css.css'
- );
- const lengthBefore = document.querySelectorAll('head style').length;
- atom.themes.requireStylesheet(cssPath);
- expect(document.querySelectorAll('head style').length).toBe(
- lengthBefore + 1
- );
- expect(styleElementAddedHandler).toHaveBeenCalled();
- const element = document.querySelector(
- 'head style[source-path*="css.css"]'
- );
- expect(element.getAttribute('source-path')).toEqualPath(cssPath);
- expect(element.textContent).toBe(fs.readFileSync(cssPath, 'utf8'));
- // doesn't append twice
- styleElementAddedHandler.reset();
- atom.themes.requireStylesheet(cssPath);
- expect(document.querySelectorAll('head style').length).toBe(
- lengthBefore + 1
- );
- expect(styleElementAddedHandler).not.toHaveBeenCalled();
- document
- .querySelectorAll('head style[id*="css.css"]')
- .forEach(styleElement => {
- styleElement.remove();
- });
- });
- it('synchronously loads and parses less files at the given path and installs a style tag for it in the head', function() {
- const lessPath = getAbsolutePath(
- atom.project.getDirectories()[0],
- 'sample.less'
- );
- const lengthBefore = document.querySelectorAll('head style').length;
- atom.themes.requireStylesheet(lessPath);
- expect(document.querySelectorAll('head style').length).toBe(
- lengthBefore + 1
- );
- const element = document.querySelector(
- 'head style[source-path*="sample.less"]'
- );
- expect(element.getAttribute('source-path')).toEqualPath(lessPath);
- expect(element.textContent.toLowerCase()).toBe(`\
- #header {
- color: #4d926f;
- }
- h2 {
- color: #4d926f;
- }
- \
- `);
- // doesn't append twice
- atom.themes.requireStylesheet(lessPath);
- expect(document.querySelectorAll('head style').length).toBe(
- lengthBefore + 1
- );
- document
- .querySelectorAll('head style[id*="sample.less"]')
- .forEach(styleElement => {
- styleElement.remove();
- });
- });
- it('supports requiring css and less stylesheets without an explicit extension', function() {
- atom.themes.requireStylesheet(path.join(__dirname, 'fixtures', 'css'));
- expect(
- document
- .querySelector('head style[source-path*="css.css"]')
- .getAttribute('source-path')
- ).toEqualPath(
- getAbsolutePath(atom.project.getDirectories()[0], 'css.css')
- );
- atom.themes.requireStylesheet(path.join(__dirname, 'fixtures', 'sample'));
- expect(
- document
- .querySelector('head style[source-path*="sample.less"]')
- .getAttribute('source-path')
- ).toEqualPath(
- getAbsolutePath(atom.project.getDirectories()[0], 'sample.less')
- );
- document.querySelector('head style[source-path*="css.css"]').remove();
- document.querySelector('head style[source-path*="sample.less"]').remove();
- });
- it('returns a disposable allowing styles applied by the given path to be removed', function() {
- const cssPath = require.resolve('./fixtures/css.css');
- expect(getComputedStyle(document.body).fontWeight).not.toBe('700');
- const disposable = atom.themes.requireStylesheet(cssPath);
- expect(getComputedStyle(document.body).fontWeight).toBe('700');
- let styleElementRemovedHandler;
- atom.styles.onDidRemoveStyleElement(
- (styleElementRemovedHandler = jasmine.createSpy(
- 'styleElementRemovedHandler'
- ))
- );
- disposable.dispose();
- expect(getComputedStyle(document.body).fontWeight).not.toBe('bold');
- expect(styleElementRemovedHandler).toHaveBeenCalled();
- });
- });
- describe('base style sheet loading', function() {
- beforeEach(function() {
- const workspaceElement = atom.workspace.getElement();
- jasmine.attachToDOM(atom.workspace.getElement());
- workspaceElement.appendChild(document.createElement('atom-text-editor'));
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it("loads the correct values from the theme's ui-variables file", function() {
- let didChangeActiveThemesHandler;
- atom.themes.onDidChangeActiveThemes(
- (didChangeActiveThemesHandler = jasmine.createSpy())
- );
- atom.config.set('core.themes', [
- 'theme-with-ui-variables',
- 'theme-with-syntax-variables'
- ]);
- waitsFor(() => didChangeActiveThemesHandler.callCount > 0);
- runs(function() {
- // an override loaded in the base css
- expect(
- getComputedStyle(atom.workspace.getElement())['background-color']
- ).toBe('rgb(0, 0, 255)');
- // from within the theme itself
- expect(
- getComputedStyle(document.querySelector('atom-text-editor'))
- .paddingTop
- ).toBe('150px');
- expect(
- getComputedStyle(document.querySelector('atom-text-editor'))
- .paddingRight
- ).toBe('150px');
- expect(
- getComputedStyle(document.querySelector('atom-text-editor'))
- .paddingBottom
- ).toBe('150px');
- });
- });
- describe('when there is a theme with incomplete variables', () =>
- it('loads the correct values from the fallback ui-variables', function() {
- let didChangeActiveThemesHandler;
- atom.themes.onDidChangeActiveThemes(
- (didChangeActiveThemesHandler = jasmine.createSpy())
- );
- atom.config.set('core.themes', [
- 'theme-with-incomplete-ui-variables',
- 'theme-with-syntax-variables'
- ]);
- waitsFor(() => didChangeActiveThemesHandler.callCount > 0);
- runs(function() {
- // an override loaded in the base css
- expect(
- getComputedStyle(atom.workspace.getElement())['background-color']
- ).toBe('rgb(0, 0, 255)');
- // from within the theme itself
- expect(
- getComputedStyle(document.querySelector('atom-text-editor'))
- .backgroundColor
- ).toBe('rgb(0, 152, 255)');
- });
- }));
- });
- describe('user stylesheet', function() {
- let userStylesheetPath;
- beforeEach(function() {
- userStylesheetPath = path.join(temp.mkdirSync('atom'), 'styles.less');
- fs.writeFileSync(
- userStylesheetPath,
- 'body {border-style: dotted !important;}'
- );
- spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(userStylesheetPath);
- });
- describe('when the user stylesheet changes', function() {
- beforeEach(() => jasmine.snapshotDeprecations());
- afterEach(() => jasmine.restoreDeprecationsSnapshot());
- it('reloads it', function() {
- let styleElementAddedHandler, styleElementRemovedHandler;
- waitsForPromise(() => atom.themes.activateThemes());
- runs(function() {
- atom.styles.onDidRemoveStyleElement(
- (styleElementRemovedHandler = jasmine.createSpy(
- 'styleElementRemovedHandler'
- ))
- );
- atom.styles.onDidAddStyleElement(
- (styleElementAddedHandler = jasmine.createSpy(
- 'styleElementAddedHandler'
- ))
- );
- spyOn(atom.themes, 'loadUserStylesheet').andCallThrough();
- expect(getComputedStyle(document.body).borderStyle).toBe('dotted');
- fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}');
- });
- waitsFor(() => atom.themes.loadUserStylesheet.callCount === 1);
- runs(function() {
- expect(getComputedStyle(document.body).borderStyle).toBe('dashed');
- expect(styleElementRemovedHandler).toHaveBeenCalled();
- expect(
- styleElementRemovedHandler.argsForCall[0][0].textContent
- ).toContain('dotted');
- expect(styleElementAddedHandler).toHaveBeenCalled();
- expect(
- styleElementAddedHandler.argsForCall[0][0].textContent
- ).toContain('dashed');
- styleElementRemovedHandler.reset();
- fs.removeSync(userStylesheetPath);
- });
- waitsFor(() => atom.themes.loadUserStylesheet.callCount === 2);
- runs(function() {
- expect(styleElementRemovedHandler).toHaveBeenCalled();
- expect(
- styleElementRemovedHandler.argsForCall[0][0].textContent
- ).toContain('dashed');
- expect(getComputedStyle(document.body).borderStyle).toBe('none');
- });
- });
- });
- describe('when there is an error reading the stylesheet', function() {
- let addErrorHandler = null;
- beforeEach(function() {
- atom.themes.loadUserStylesheet();
- spyOn(atom.themes.lessCache, 'cssForFile').andCallFake(function() {
- throw new Error('EACCES permission denied "styles.less"');
- });
- atom.notifications.onDidAddNotification(
- (addErrorHandler = jasmine.createSpy())
- );
- });
- it('creates an error notification and does not add the stylesheet', function() {
- atom.themes.loadUserStylesheet();
- expect(addErrorHandler).toHaveBeenCalled();
- const note = addErrorHandler.mostRecentCall.args[0];
- expect(note.getType()).toBe('error');
- expect(note.getMessage()).toContain('Error loading');
- expect(
- atom.styles.styleElementsBySourcePath[
- atom.styles.getUserStyleSheetPath()
- ]
- ).toBeUndefined();
- });
- });
- describe('when there is an error watching the user stylesheet', function() {
- let addErrorHandler = null;
- beforeEach(function() {
- const { File } = require('pathwatcher');
- spyOn(File.prototype, 'on').andCallFake(function(event) {
- if (event.indexOf('contents-changed') > -1) {
- throw new Error('Unable to watch path');
- }
- });
- spyOn(atom.themes, 'loadStylesheet').andReturn('');
- atom.notifications.onDidAddNotification(
- (addErrorHandler = jasmine.createSpy())
- );
- });
- it('creates an error notification', function() {
- atom.themes.loadUserStylesheet();
- expect(addErrorHandler).toHaveBeenCalled();
- const note = addErrorHandler.mostRecentCall.args[0];
- expect(note.getType()).toBe('error');
- expect(note.getMessage()).toContain('Unable to watch path');
- });
- });
- it("adds a notification when a theme's stylesheet is invalid", function() {
- const addErrorHandler = jasmine.createSpy();
- atom.notifications.onDidAddNotification(addErrorHandler);
- expect(() =>
- atom.packages
- .activatePackage('theme-with-invalid-styles')
- .then(function() {}, function() {})
- ).not.toThrow();
- expect(addErrorHandler.callCount).toBe(2);
- expect(addErrorHandler.argsForCall[1][0].message).toContain(
- 'Failed to activate the theme-with-invalid-styles theme'
- );
- });
- });
- describe('when a non-existent theme is present in the config', function() {
- beforeEach(function() {
- console.warn.reset();
- atom.config.set('core.themes', [
- 'non-existent-dark-ui',
- 'non-existent-dark-syntax'
- ]);
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it('uses the default one-dark UI and syntax themes and logs a warning', function() {
- const activeThemeNames = atom.themes.getActiveThemeNames();
- expect(console.warn.callCount).toBe(2);
- expect(activeThemeNames.length).toBe(2);
- expect(activeThemeNames).toContain('one-dark-ui');
- expect(activeThemeNames).toContain('one-dark-syntax');
- });
- });
- describe('when in safe mode', function() {
- describe('when the enabled UI and syntax themes are bundled with Atom', function() {
- beforeEach(function() {
- atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-syntax']);
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it('uses the enabled themes', function() {
- const activeThemeNames = atom.themes.getActiveThemeNames();
- expect(activeThemeNames.length).toBe(2);
- expect(activeThemeNames).toContain('atom-light-ui');
- expect(activeThemeNames).toContain('atom-dark-syntax');
- });
- });
- describe('when the enabled UI and syntax themes are not bundled with Atom', function() {
- beforeEach(function() {
- atom.config.set('core.themes', [
- 'installed-dark-ui',
- 'installed-dark-syntax'
- ]);
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it('uses the default dark UI and syntax themes', function() {
- const activeThemeNames = atom.themes.getActiveThemeNames();
- expect(activeThemeNames.length).toBe(2);
- expect(activeThemeNames).toContain('one-dark-ui');
- expect(activeThemeNames).toContain('one-dark-syntax');
- });
- });
- describe('when the enabled UI theme is not bundled with Atom', function() {
- beforeEach(function() {
- atom.config.set('core.themes', [
- 'installed-dark-ui',
- 'atom-light-syntax'
- ]);
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it('uses the default one-dark UI theme', function() {
- const activeThemeNames = atom.themes.getActiveThemeNames();
- expect(activeThemeNames.length).toBe(2);
- expect(activeThemeNames).toContain('one-dark-ui');
- expect(activeThemeNames).toContain('atom-light-syntax');
- });
- });
- describe('when the enabled syntax theme is not bundled with Atom', function() {
- beforeEach(function() {
- atom.config.set('core.themes', [
- 'atom-light-ui',
- 'installed-dark-syntax'
- ]);
- waitsForPromise(() => atom.themes.activateThemes());
- });
- it('uses the default one-dark syntax theme', function() {
- const activeThemeNames = atom.themes.getActiveThemeNames();
- expect(activeThemeNames.length).toBe(2);
- expect(activeThemeNames).toContain('atom-light-ui');
- expect(activeThemeNames).toContain('one-dark-syntax');
- });
- });
- });
- });
- function getAbsolutePath(directory, relativePath) {
- if (directory) {
- return directory.resolve(relativePath);
- }
- }
|