├── .github └── no-response.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── appveyor.yml ├── keymaps └── dev-live-reload.cson ├── lib ├── base-theme-watcher.js ├── main.js ├── package-watcher.js ├── ui-watcher.js └── watcher.js ├── menus └── dev-live-reload.cson ├── package.json └── spec ├── async-spec-helpers.js ├── dev-live-reload-spec.js ├── fixtures ├── package-with-index │ └── index.coffee ├── package-with-styles-folder │ ├── package.cson │ └── styles │ │ ├── 3.css │ │ └── sub │ │ ├── 1.css │ │ └── 2.less ├── package-with-styles-manifest │ ├── package.cson │ └── styles │ │ ├── 1.css │ │ ├── 2.less │ │ └── 3.css ├── packages │ ├── index.less │ ├── package.cson │ ├── package.json │ └── styles │ │ ├── 1.css │ │ ├── 2.less │ │ ├── 3.css │ │ ├── first.less │ │ ├── last.less │ │ ├── second.less │ │ └── ui-variables.less ├── static │ └── atom.less ├── theme-with-index-less │ ├── index.less │ └── package.json ├── theme-with-multiple-imported-files │ ├── index.less │ ├── package.json │ └── styles │ │ ├── first.less │ │ ├── last.less │ │ ├── second.less │ │ └── ui-variables.less ├── theme-with-package-file │ ├── package.json │ └── styles │ │ ├── first.css │ │ ├── last.css │ │ └── second.less ├── theme-with-syntax-variables │ ├── package.json │ └── styles │ │ └── editor.less └── theme-with-ui-variables │ ├── package.json │ └── styles │ ├── editor.less │ └── ui-variables.less └── ui-watcher-spec.js /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 180 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | language: generic 3 | 4 | env: 5 | global: 6 | - APM_TEST_PACKAGES="" 7 | - ATOM_LINT_WITH_BUNDLED_NODE="true" 8 | 9 | matrix: 10 | - ATOM_CHANNEL=stable 11 | - ATOM_CHANNEL=beta 12 | 13 | ### Generic setup follows ### 14 | script: 15 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh 16 | - chmod u+x build-package.sh 17 | - ./build-package.sh 18 | 19 | notifications: 20 | email: 21 | on_success: never 22 | on_failure: change 23 | 24 | branches: 25 | only: 26 | - master 27 | 28 | git: 29 | depth: 10 30 | 31 | sudo: false 32 | 33 | dist: trusty 34 | 35 | addons: 36 | apt: 37 | packages: 38 | - build-essential 39 | - fakeroot 40 | - git 41 | - libsecret-1-dev 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This package is now a part of the [core Atom repository](https://github.com/atom/atom/tree/master/packages/dev-live-reload), please direct all issues and pull requests there in the future! 2 | 3 | --- 4 | 5 | # Dev Live Reload package 6 | [![OS X Build Status](https://travis-ci.org/atom/dev-live-reload.svg?branch=master)](https://travis-ci.org/atom/dev-live-reload) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/g3sd27ylba1fun1v/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/dev-live-reload/branch/master) [![Dependency Status](https://david-dm.org/atom/dev-live-reload.svg)](https://david-dm.org/atom/dev-live-reload) 7 | 8 | This live reloads the Atom `.less` files. You edit styles and they are magically reflected in any running Atom windows. Magic! :tophat: :sparkles: :rabbit2: 9 | 10 | Installed by default on Atom windows running in dev mode. Use the "Application: Open Dev" command to open a new dev mode window. 11 | 12 | Use meta-shift-ctrl-r to reload all core and package stylesheets. 13 | 14 | This package is __experimental__, it does not handle the following: 15 | 16 | * File additions to a theme. New files will not be watched. 17 | 18 | ![gif](https://f.cloud.github.com/assets/69169/1387004/d2dc45f2-3b84-11e3-877e-cac8c51e9702.gif) 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /keymaps/dev-live-reload.cson: -------------------------------------------------------------------------------- 1 | '.platform-darwin': 2 | 'cmd-ctrl-R': 'dev-live-reload:reload-all' 3 | 4 | '.platform-win32': 5 | 'alt-ctrl-R': 'dev-live-reload:reload-all' 6 | -------------------------------------------------------------------------------- /lib/base-theme-watcher.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-plus') 2 | const path = require('path') 3 | const Watcher = require('./watcher') 4 | 5 | module.exports = 6 | class BaseThemeWatcher extends Watcher { 7 | constructor () { 8 | super() 9 | this.stylesheetsPath = path.dirname(atom.themes.resolveStylesheet('../static/atom.less')) 10 | this.watch() 11 | } 12 | 13 | watch () { 14 | const filePaths = fs.readdirSync(this.stylesheetsPath).filter(filePath => path.extname(filePath).includes('less')) 15 | 16 | for (const filePath of filePaths) { 17 | this.watchFile(path.join(this.stylesheetsPath, filePath)) 18 | } 19 | } 20 | 21 | loadStylesheet () { 22 | this.loadAllStylesheets() 23 | } 24 | 25 | loadAllStylesheets () { 26 | atom.themes.reloadBaseStylesheets() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | activate (state) { 3 | if (!atom.inDevMode() || atom.inSpecMode()) return 4 | 5 | if (atom.packages.hasActivatedInitialPackages()) { 6 | this.startWatching() 7 | } else { 8 | this.activatedDisposable = atom.packages.onDidActivateInitialPackages(() => this.startWatching()) 9 | } 10 | }, 11 | 12 | deactivate () { 13 | if (this.activatedDisposable) this.activatedDisposable.dispose() 14 | if (this.commandDisposable) this.commandDisposable.dispose() 15 | if (this.uiWatcher) this.uiWatcher.destroy() 16 | }, 17 | 18 | startWatching () { 19 | const UIWatcher = require('./ui-watcher') 20 | this.uiWatcher = new UIWatcher({themeManager: atom.themes}) 21 | this.commandDisposable = atom.commands.add('atom-workspace', 'dev-live-reload:reload-all', () => this.uiWatcher.reloadAll()) 22 | if (this.activatedDisposable) this.activatedDisposable.dispose() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/package-watcher.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-plus') 2 | 3 | const Watcher = require('./watcher') 4 | 5 | module.exports = 6 | class PackageWatcher extends Watcher { 7 | static supportsPackage (pack, type) { 8 | if (pack.getType() === type && pack.getStylesheetPaths().length) return true 9 | return false 10 | } 11 | 12 | constructor (pack) { 13 | super() 14 | this.pack = pack 15 | this.watch() 16 | } 17 | 18 | watch () { 19 | const watchedPaths = [] 20 | const watchPath = stylesheet => { 21 | if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet) 22 | watchedPaths.push(stylesheet) 23 | } 24 | 25 | const stylesheetsPath = this.pack.getStylesheetsPath() 26 | 27 | if (fs.isDirectorySync(stylesheetsPath)) this.watchDirectory(stylesheetsPath) 28 | 29 | const stylesheetPaths = new Set(this.pack.getStylesheetPaths()) 30 | const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath) 31 | const onFolder = () => true 32 | fs.traverseTreeSync(stylesheetsPath, onFile, onFolder) 33 | 34 | for (let stylesheet of stylesheetPaths) { 35 | watchPath(stylesheet) 36 | } 37 | } 38 | 39 | loadStylesheet (pathName) { 40 | if (pathName.includes('variables')) this.emitGlobalsChanged() 41 | this.loadAllStylesheets() 42 | } 43 | 44 | loadAllStylesheets () { 45 | console.log('Reloading package', this.pack.name) 46 | this.pack.reloadStylesheets() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/ui-watcher.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable} = require('atom') 2 | 3 | const BaseThemeWatcher = require('./base-theme-watcher') 4 | const PackageWatcher = require('./package-watcher') 5 | 6 | module.exports = 7 | class UIWatcher { 8 | constructor () { 9 | this.subscriptions = new CompositeDisposable() 10 | this.reloadAll = this.reloadAll.bind(this) 11 | this.watchers = [] 12 | this.baseTheme = this.createWatcher(new BaseThemeWatcher()) 13 | this.watchPackages() 14 | } 15 | 16 | watchPackages () { 17 | this.watchedThemes = new Map() 18 | this.watchedPackages = new Map() 19 | for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) } 20 | for (const pack of atom.packages.getActivePackages()) { this.watchPackage(pack) } 21 | this.watchForPackageChanges() 22 | } 23 | 24 | watchForPackageChanges () { 25 | this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => { 26 | // We need to destroy all theme watchers as all theme packages are destroyed 27 | // when a theme changes. 28 | for (const theme of this.watchedThemes.values()) { theme.destroy() } 29 | 30 | this.watchedThemes.clear() 31 | 32 | // Rewatch everything! 33 | for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) } 34 | })) 35 | 36 | this.subscriptions.add(atom.packages.onDidActivatePackage(pack => this.watchPackage(pack))) 37 | 38 | this.subscriptions.add(atom.packages.onDidDeactivatePackage(pack => { 39 | // This only handles packages - onDidChangeActiveThemes handles themes 40 | const watcher = this.watchedPackages.get(pack.name) 41 | if (watcher) watcher.destroy() 42 | this.watchedPackages.delete(pack.name) 43 | })) 44 | } 45 | 46 | watchTheme (theme) { 47 | if (PackageWatcher.supportsPackage(theme, 'theme')) this.watchedThemes.set(theme.name, this.createWatcher(new PackageWatcher(theme))) 48 | } 49 | 50 | watchPackage (pack) { 51 | if (PackageWatcher.supportsPackage(pack, 'atom')) this.watchedPackages.set(pack.name, this.createWatcher(new PackageWatcher(pack))) 52 | } 53 | 54 | createWatcher (watcher) { 55 | watcher.onDidChangeGlobals(() => { 56 | console.log('Global changed, reloading all styles') 57 | this.reloadAll() 58 | }) 59 | watcher.onDidDestroy(() => this.watchers.splice(this.watchers.indexOf(watcher), 1)) 60 | this.watchers.push(watcher) 61 | return watcher 62 | } 63 | 64 | reloadAll () { 65 | this.baseTheme.loadAllStylesheets() 66 | for (const pack of atom.packages.getActivePackages()) { 67 | if (PackageWatcher.supportsPackage(pack, 'atom')) pack.reloadStylesheets() 68 | } 69 | 70 | for (const theme of atom.themes.getActiveThemes()) { 71 | if (PackageWatcher.supportsPackage(theme, 'theme')) theme.reloadStylesheets() 72 | } 73 | } 74 | 75 | destroy () { 76 | this.subscriptions.dispose() 77 | this.baseTheme.destroy() 78 | for (const pack of this.watchedPackages.values()) { pack.destroy() } 79 | for (const theme of this.watchedThemes.values()) { theme.destroy() } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/watcher.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable, File, Directory, Emitter} = require('atom') 2 | const path = require('path') 3 | 4 | module.exports = 5 | class Watcher { 6 | constructor () { 7 | this.destroy = this.destroy.bind(this) 8 | this.emitter = new Emitter() 9 | this.disposables = new CompositeDisposable() 10 | this.entities = [] // Used for specs 11 | } 12 | 13 | onDidDestroy (callback) { 14 | this.emitter.on('did-destroy', callback) 15 | } 16 | 17 | onDidChangeGlobals (callback) { 18 | this.emitter.on('did-change-globals', callback) 19 | } 20 | 21 | destroy () { 22 | this.disposables.dispose() 23 | this.entities = null 24 | this.emitter.emit('did-destroy') 25 | this.emitter.dispose() 26 | } 27 | 28 | watch () { 29 | // override me 30 | } 31 | 32 | loadStylesheet (stylesheetPath) { 33 | // override me 34 | } 35 | 36 | loadAllStylesheets () { 37 | // override me 38 | } 39 | 40 | emitGlobalsChanged () { 41 | this.emitter.emit('did-change-globals') 42 | } 43 | 44 | watchDirectory (directoryPath) { 45 | if (this.isInAsarArchive(directoryPath)) return 46 | const entity = new Directory(directoryPath) 47 | this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets())) 48 | this.entities.push(entity) 49 | } 50 | 51 | watchGlobalFile (filePath) { 52 | const entity = new File(filePath) 53 | this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged())) 54 | this.entities.push(entity) 55 | } 56 | 57 | watchFile (filePath) { 58 | if (this.isInAsarArchive(filePath)) return 59 | const reloadFn = () => this.loadStylesheet(entity.getPath()) 60 | 61 | const entity = new File(filePath) 62 | this.disposables.add(entity.onDidChange(reloadFn)) 63 | this.disposables.add(entity.onDidDelete(reloadFn)) 64 | this.disposables.add(entity.onDidRename(reloadFn)) 65 | this.entities.push(entity) 66 | } 67 | 68 | isInAsarArchive (pathToCheck) { 69 | const {resourcePath} = atom.getLoadSettings() 70 | return pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /menus/dev-live-reload.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | 'label': 'Packages' 3 | 'submenu': [ 4 | 'label': 'Dev Live Reload' 5 | 'submenu': [ 6 | { 'label': 'Reload All Styles', 'command': 'dev-live-reload:reload-all' } 7 | ] 8 | ] 9 | ] 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev-live-reload", 3 | "main": "./lib/main", 4 | "version": "0.48.1", 5 | "description": "Live reload atom themes and packages.", 6 | "repository": "https://github.com/atom/dev-live-reload", 7 | "license": "MIT", 8 | "dependencies": { 9 | "fs-plus": "^3.0.0" 10 | }, 11 | "engines": { 12 | "atom": "*" 13 | }, 14 | "devDependencies": { 15 | "standard": "^10.0.3" 16 | }, 17 | "standard": { 18 | "env": { 19 | "atomtest": true, 20 | "browser": true, 21 | "jasmine": true, 22 | "node": true 23 | }, 24 | "globals": [ 25 | "atom" 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export function beforeEach (fn) { 4 | global.beforeEach(function () { 5 | const result = fn() 6 | if (result instanceof Promise) { 7 | waitsForPromise(() => result) 8 | } 9 | }) 10 | } 11 | 12 | export function afterEach (fn) { 13 | global.afterEach(function () { 14 | const result = fn() 15 | if (result instanceof Promise) { 16 | waitsForPromise(() => result) 17 | } 18 | }) 19 | } 20 | 21 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { 22 | module.exports[name] = function (description, fn) { 23 | if (fn === undefined) { 24 | global[name](description) 25 | return 26 | } 27 | 28 | global[name](description, function () { 29 | const result = fn() 30 | if (result instanceof Promise) { 31 | waitsForPromise(() => result) 32 | } 33 | }) 34 | } 35 | }) 36 | 37 | export async function conditionPromise (condition, description = 'anonymous condition') { 38 | const startTime = Date.now() 39 | 40 | while (true) { 41 | await timeoutPromise(100) 42 | 43 | if (await condition()) { 44 | return 45 | } 46 | 47 | if (Date.now() - startTime > 5000) { 48 | throw new Error('Timed out waiting on ' + description) 49 | } 50 | } 51 | } 52 | 53 | export function timeoutPromise (timeout) { 54 | return new Promise(function (resolve) { 55 | global.setTimeout(resolve, timeout) 56 | }) 57 | } 58 | 59 | function waitsForPromise (fn) { 60 | const promise = fn() 61 | global.waitsFor('spec promise to resolve', function (done) { 62 | promise.then(done, function (error) { 63 | jasmine.getEnv().currentSpec.fail(error) 64 | done() 65 | }) 66 | }) 67 | } 68 | 69 | export function emitterEventPromise (emitter, event, timeout = 15000) { 70 | return new Promise((resolve, reject) => { 71 | const timeoutHandle = setTimeout(() => { 72 | reject(new Error(`Timed out waiting for '${event}' event`)) 73 | }, timeout) 74 | emitter.once(event, () => { 75 | clearTimeout(timeoutHandle) 76 | resolve() 77 | }) 78 | }) 79 | } 80 | 81 | export function promisify (original) { 82 | return function (...args) { 83 | return new Promise((resolve, reject) => { 84 | args.push((err, ...results) => { 85 | if (err) { 86 | reject(err) 87 | } else { 88 | resolve(...results) 89 | } 90 | }) 91 | 92 | return original(...args) 93 | }) 94 | } 95 | } 96 | 97 | export function promisifySome (obj, fnNames) { 98 | const result = {} 99 | for (const fnName of fnNames) { 100 | result[fnName] = promisify(obj[fnName]) 101 | } 102 | return result 103 | } 104 | -------------------------------------------------------------------------------- /spec/dev-live-reload-spec.js: -------------------------------------------------------------------------------- 1 | const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 2 | 3 | describe('Dev Live Reload', () => { 4 | describe('package activation', () => { 5 | let [pack, mainModule] = [] 6 | 7 | beforeEach(() => { 8 | pack = atom.packages.loadPackage('dev-live-reload') 9 | pack.requireMainModule() 10 | mainModule = pack.mainModule 11 | spyOn(mainModule, 'startWatching') 12 | }) 13 | 14 | describe('when the window is not in dev mode', () => { 15 | beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false)) 16 | 17 | it('does not watch files', async () => { 18 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 19 | 20 | await atom.packages.activatePackage('dev-live-reload') 21 | expect(mainModule.startWatching).not.toHaveBeenCalled() 22 | }) 23 | }) 24 | 25 | describe('when the window is in spec mode', () => { 26 | beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true)) 27 | 28 | it('does not watch files', async () => { 29 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 30 | 31 | await atom.packages.activatePackage('dev-live-reload') 32 | expect(mainModule.startWatching).not.toHaveBeenCalled() 33 | }) 34 | }) 35 | 36 | describe('when the window is in dev mode', () => { 37 | beforeEach(() => { 38 | spyOn(atom, 'inDevMode').andReturn(true) 39 | spyOn(atom, 'inSpecMode').andReturn(false) 40 | }) 41 | 42 | it('watches files', async () => { 43 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 44 | 45 | await atom.packages.activatePackage('dev-live-reload') 46 | expect(mainModule.startWatching).toHaveBeenCalled() 47 | }) 48 | }) 49 | 50 | describe('when the window is in both dev mode and spec mode', () => { 51 | beforeEach(() => { 52 | spyOn(atom, 'inDevMode').andReturn(true) 53 | spyOn(atom, 'inSpecMode').andReturn(true) 54 | }) 55 | 56 | it('does not watch files', async () => { 57 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 58 | 59 | await atom.packages.activatePackage('dev-live-reload') 60 | expect(mainModule.startWatching).not.toHaveBeenCalled() 61 | }) 62 | }) 63 | 64 | describe('when the package is activated before initial packages have been activated', () => { 65 | beforeEach(() => { 66 | spyOn(atom, 'inDevMode').andReturn(true) 67 | spyOn(atom, 'inSpecMode').andReturn(false) 68 | }) 69 | 70 | it('waits until all initial packages have been activated before watching files', async () => { 71 | await atom.packages.activatePackage('dev-live-reload') 72 | expect(mainModule.startWatching).not.toHaveBeenCalled() 73 | 74 | atom.packages.emitter.emit('did-activate-initial-packages') 75 | expect(mainModule.startWatching).toHaveBeenCalled() 76 | }) 77 | }) 78 | }) 79 | 80 | describe('package deactivation', () => { 81 | beforeEach(() => { 82 | spyOn(atom, 'inDevMode').andReturn(true) 83 | spyOn(atom, 'inSpecMode').andReturn(false) 84 | }) 85 | 86 | it('stops watching all files', async () => { 87 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 88 | const {mainModule} = await atom.packages.activatePackage('dev-live-reload') 89 | expect(mainModule.uiWatcher).not.toBeNull() 90 | 91 | spyOn(mainModule.uiWatcher, 'destroy') 92 | 93 | await atom.packages.deactivatePackage('dev-live-reload') 94 | expect(mainModule.uiWatcher.destroy).toHaveBeenCalled() 95 | }) 96 | 97 | it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => { 98 | const {mainModule} = await atom.packages.activatePackage('dev-live-reload') 99 | expect(mainModule.activatedDisposable.disposed).toBe(false) 100 | 101 | await atom.packages.deactivatePackage('dev-live-reload') 102 | expect(mainModule.activatedDisposable.disposed).toBe(true) 103 | 104 | spyOn(mainModule, 'startWatching') 105 | atom.packages.emitter.emit('did-activate-initial-packages') 106 | expect(mainModule.startWatching).not.toHaveBeenCalled() 107 | }) 108 | 109 | it('removes its commands', async () => { 110 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 111 | await atom.packages.activatePackage('dev-live-reload') 112 | expect(atom.commands 113 | .findCommands({target: atom.views.getView(atom.workspace)}) 114 | .filter(command => command.name.startsWith('dev-live-reload')) 115 | .length).toBeGreaterThan(0) 116 | 117 | await atom.packages.deactivatePackage('dev-live-reload') 118 | expect(atom.commands 119 | .findCommands({target: atom.views.getView(atom.workspace)}) 120 | .filter(command => command.name.startsWith('dev-live-reload')) 121 | .length).toBe(0) 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-index/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | activate: -> 3 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-folder/package.cson: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-folder/styles/3.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 3px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-folder/styles/sub/1.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 1px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-folder/styles/sub/2.less: -------------------------------------------------------------------------------- 1 | @size: 2px; 2 | 3 | #jasmine-content { 4 | font-size: @size; 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-manifest/package.cson: -------------------------------------------------------------------------------- 1 | styleSheets: ['2', '1'] 2 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-manifest/styles/1.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 1px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-manifest/styles/2.less: -------------------------------------------------------------------------------- 1 | @size: 2px; 2 | 3 | #jasmine-content { 4 | font-size: @size; 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/package-with-styles-manifest/styles/3.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 3px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/packages/index.less: -------------------------------------------------------------------------------- 1 | @import "styles/first"; 2 | @import "styles/second"; 3 | @import "styles/last"; 4 | -------------------------------------------------------------------------------- /spec/fixtures/packages/package.cson: -------------------------------------------------------------------------------- 1 | styleSheets: ['2', '1'] 2 | -------------------------------------------------------------------------------- /spec/fixtures/packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": true 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/1.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 1px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/2.less: -------------------------------------------------------------------------------- 1 | @size: 2px; 2 | 3 | #jasmine-content { 4 | font-size: @size; 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/3.css: -------------------------------------------------------------------------------- 1 | #jasmine-content { 2 | font-size: 3px; 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/first.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | padding-top: 101px; 3 | padding-right: 101px; 4 | padding-bottom: 101px; 5 | 6 | color: red; 7 | } -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/last.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | /* padding-top: 103px; 3 | padding-right: 103px;*/ 4 | padding-bottom: 103px; 5 | } -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/second.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | @number: 102px; 4 | 5 | .editor { 6 | /* padding-top: 102px;*/ 7 | padding-right: @number; 8 | padding-bottom: @number; 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/packages/styles/ui-variables.less: -------------------------------------------------------------------------------- 1 | // Variables different from the original are marked 'Changed' 2 | 3 | @text-color: #333; 4 | @text-color-subtle: #777; 5 | @text-color-highlight: #111; 6 | @text-color-selected: @text-color-highlight; 7 | 8 | @text-color-info: #5293d8; 9 | @text-color-success: #1fe977; 10 | @text-color-warning: #f78a46; 11 | @text-color-error: #c00; 12 | 13 | @background-color-info: #0098ff; 14 | @background-color-success: #17ca65; 15 | @background-color-warning: #ff4800; 16 | @background-color-error: #c00; 17 | @background-color-highlight: rgba(255, 255, 255, 0.10); 18 | @background-color-selected: @background-color-highlight; 19 | 20 | @app-background-color: #00f; // Changed 21 | 22 | @base-background-color: #fff; 23 | @base-border-color: #eee; 24 | 25 | @pane-item-background-color: @base-background-color; 26 | @pane-item-border-color: @base-border-color; 27 | 28 | @input-background-color: #f00; // Changed 29 | @input-border-color: @base-border-color; 30 | 31 | @tool-panel-background-color: #f4f4f4; 32 | @tool-panel-border-color: @base-border-color; 33 | 34 | @inset-panel-background-color: #eee; 35 | @inset-panel-border-color: @base-border-color; 36 | 37 | @panel-heading-background-color: #ddd; 38 | @panel-heading-border-color: transparent; 39 | 40 | @overlay-background-color: #f4f4f4; 41 | @overlay-border-color: @base-border-color; 42 | 43 | @button-background-color: #ccc; 44 | @button-background-color-hover: lighten(@button-background-color, 5%); 45 | @button-background-color-selected: @button-background-color-hover; 46 | @button-border-color: #aaa; 47 | 48 | @tab-bar-background-color: #fff; 49 | @tab-bar-border-color: darken(@tab-background-color-active, 10%); 50 | @tab-background-color: #f4f4f4; 51 | @tab-background-color-active: #fff; 52 | @tab-border-color: @base-border-color; 53 | 54 | @tree-view-background-color: @tool-panel-background-color; 55 | @tree-view-border-color: @tool-panel-border-color; 56 | 57 | @ui-site-color-1: @background-color-success; // green 58 | @ui-site-color-2: @background-color-info; // blue 59 | @ui-site-color-3: @background-color-warning; // orange 60 | @ui-site-color-4: #db2ff4; // purple 61 | @ui-site-color-5: #f5e11d; // yellow 62 | 63 | @font-size: 12px; 64 | 65 | @disclosure-arrow-size: 12px; 66 | 67 | @component-padding: 150px; 68 | @component-icon-padding: 5px; 69 | @component-icon-size: 16px; 70 | @component-line-height: 25px; 71 | @component-border-radius: 2px; 72 | 73 | @tab-height: 30px; 74 | 75 | @font-family: Arial; 76 | -------------------------------------------------------------------------------- /spec/fixtures/static/atom.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atom/dev-live-reload/3f0e430b6a0b413d8669a8ada5db45c8fb46e04f/spec/fixtures/static/atom.less -------------------------------------------------------------------------------- /spec/fixtures/theme-with-index-less/index.less: -------------------------------------------------------------------------------- 1 | @padding: 4321px; 2 | 3 | atom-text-editor { 4 | padding-top: @padding; 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-index-less/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "ui" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/index.less: -------------------------------------------------------------------------------- 1 | @import "styles/first"; 2 | @import "styles/second"; 3 | @import "styles/last"; 4 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "theme-with-multiple-imported-files", 3 | "theme": "ui" 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/styles/first.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | padding-top: 101px; 3 | padding-right: 101px; 4 | padding-bottom: 101px; 5 | 6 | color: red; 7 | } -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/styles/last.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | /* padding-top: 103px; 3 | padding-right: 103px;*/ 4 | padding-bottom: 103px; 5 | } -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/styles/second.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | @number: 102px; 4 | 5 | .editor { 6 | /* padding-top: 102px;*/ 7 | padding-right: @number; 8 | padding-bottom: @number; 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-multiple-imported-files/styles/ui-variables.less: -------------------------------------------------------------------------------- 1 | // Variables different from the original are marked 'Changed' 2 | 3 | @text-color: #333; 4 | @text-color-subtle: #777; 5 | @text-color-highlight: #111; 6 | @text-color-selected: @text-color-highlight; 7 | 8 | @text-color-info: #5293d8; 9 | @text-color-success: #1fe977; 10 | @text-color-warning: #f78a46; 11 | @text-color-error: #c00; 12 | 13 | @background-color-info: #0098ff; 14 | @background-color-success: #17ca65; 15 | @background-color-warning: #ff4800; 16 | @background-color-error: #c00; 17 | @background-color-highlight: rgba(255, 255, 255, 0.10); 18 | @background-color-selected: @background-color-highlight; 19 | 20 | @app-background-color: #00f; // Changed 21 | 22 | @base-background-color: #fff; 23 | @base-border-color: #eee; 24 | 25 | @pane-item-background-color: @base-background-color; 26 | @pane-item-border-color: @base-border-color; 27 | 28 | @input-background-color: #f00; // Changed 29 | @input-border-color: @base-border-color; 30 | 31 | @tool-panel-background-color: #f4f4f4; 32 | @tool-panel-border-color: @base-border-color; 33 | 34 | @inset-panel-background-color: #eee; 35 | @inset-panel-border-color: @base-border-color; 36 | 37 | @panel-heading-background-color: #ddd; 38 | @panel-heading-border-color: transparent; 39 | 40 | @overlay-background-color: #f4f4f4; 41 | @overlay-border-color: @base-border-color; 42 | 43 | @button-background-color: #ccc; 44 | @button-background-color-hover: lighten(@button-background-color, 5%); 45 | @button-background-color-selected: @button-background-color-hover; 46 | @button-border-color: #aaa; 47 | 48 | @tab-bar-background-color: #fff; 49 | @tab-bar-border-color: darken(@tab-background-color-active, 10%); 50 | @tab-background-color: #f4f4f4; 51 | @tab-background-color-active: #fff; 52 | @tab-border-color: @base-border-color; 53 | 54 | @tree-view-background-color: @tool-panel-background-color; 55 | @tree-view-border-color: @tool-panel-border-color; 56 | 57 | @ui-site-color-1: @background-color-success; // green 58 | @ui-site-color-2: @background-color-info; // blue 59 | @ui-site-color-3: @background-color-warning; // orange 60 | @ui-site-color-4: #db2ff4; // purple 61 | @ui-site-color-5: #f5e11d; // yellow 62 | 63 | @font-size: 12px; 64 | 65 | @disclosure-arrow-size: 12px; 66 | 67 | @component-padding: 150px; 68 | @component-icon-padding: 5px; 69 | @component-icon-size: 16px; 70 | @component-line-height: 25px; 71 | @component-border-radius: 2px; 72 | 73 | @tab-height: 30px; 74 | 75 | @font-family: Arial; 76 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-package-file/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "ui", 3 | "styleSheets": ["first.css", "second.less", "last.css"] 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-package-file/styles/first.css: -------------------------------------------------------------------------------- 1 | atom-text-editor { 2 | padding-top: 101px; 3 | padding-right: 101px; 4 | padding-bottom: 101px; 5 | 6 | color: red; 7 | } -------------------------------------------------------------------------------- /spec/fixtures/theme-with-package-file/styles/last.css: -------------------------------------------------------------------------------- 1 | atom-text-editor { 2 | /* padding-top: 103px; 3 | padding-right: 103px;*/ 4 | padding-bottom: 103px; 5 | } -------------------------------------------------------------------------------- /spec/fixtures/theme-with-package-file/styles/second.less: -------------------------------------------------------------------------------- 1 | @number: 102px; 2 | 3 | atom-text-editor { 4 | /* padding-top: 102px;*/ 5 | padding-right: @number; 6 | padding-bottom: @number; 7 | } -------------------------------------------------------------------------------- /spec/fixtures/theme-with-syntax-variables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "syntax", 3 | "styleSheets": ["editor.less"] 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-syntax-variables/styles/editor.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atom/dev-live-reload/3f0e430b6a0b413d8669a8ada5db45c8fb46e04f/spec/fixtures/theme-with-syntax-variables/styles/editor.less -------------------------------------------------------------------------------- /spec/fixtures/theme-with-ui-variables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "ui", 3 | "styleSheets": ["editor.less"] 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-ui-variables/styles/editor.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | atom-text-editor { 4 | padding-top: @component-padding; 5 | padding-right: @component-padding; 6 | padding-bottom: @component-padding; 7 | 8 | color: @input-background-color; 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/theme-with-ui-variables/styles/ui-variables.less: -------------------------------------------------------------------------------- 1 | // Variables different from the original are marked 'Changed' 2 | 3 | @text-color: #333; 4 | @text-color-subtle: #777; 5 | @text-color-highlight: #111; 6 | @text-color-selected: @text-color-highlight; 7 | 8 | @text-color-info: #5293d8; 9 | @text-color-success: #1fe977; 10 | @text-color-warning: #f78a46; 11 | @text-color-error: #c00; 12 | 13 | @background-color-info: #0098ff; 14 | @background-color-success: #17ca65; 15 | @background-color-warning: #ff4800; 16 | @background-color-error: #c00; 17 | @background-color-highlight: rgba(255, 255, 255, 0.10); 18 | @background-color-selected: @background-color-highlight; 19 | 20 | @app-background-color: #00f; // Changed 21 | 22 | @base-background-color: #fff; 23 | @base-border-color: #eee; 24 | 25 | @pane-item-background-color: @base-background-color; 26 | @pane-item-border-color: @base-border-color; 27 | 28 | @input-background-color: #f00; // Changed 29 | @input-border-color: @base-border-color; 30 | 31 | @tool-panel-background-color: #f4f4f4; 32 | @tool-panel-border-color: @base-border-color; 33 | 34 | @inset-panel-background-color: #eee; 35 | @inset-panel-border-color: @base-border-color; 36 | 37 | @panel-heading-background-color: #ddd; 38 | @panel-heading-border-color: transparent; 39 | 40 | @overlay-background-color: #f4f4f4; 41 | @overlay-border-color: @base-border-color; 42 | 43 | @button-background-color: #ccc; 44 | @button-background-color-hover: lighten(@button-background-color, 5%); 45 | @button-background-color-selected: @button-background-color-hover; 46 | @button-border-color: #aaa; 47 | 48 | @tab-bar-background-color: #fff; 49 | @tab-bar-border-color: darken(@tab-background-color-active, 10%); 50 | @tab-background-color: #f4f4f4; 51 | @tab-background-color-active: #fff; 52 | @tab-border-color: @base-border-color; 53 | 54 | @tree-view-background-color: @tool-panel-background-color; 55 | @tree-view-border-color: @tool-panel-border-color; 56 | 57 | @ui-site-color-1: @background-color-success; // green 58 | @ui-site-color-2: @background-color-info; // blue 59 | @ui-site-color-3: @background-color-warning; // orange 60 | @ui-site-color-4: #db2ff4; // purple 61 | @ui-site-color-5: #f5e11d; // yellow 62 | 63 | @font-size: 12px; 64 | 65 | @disclosure-arrow-size: 12px; 66 | 67 | @component-padding: 150px; 68 | @component-icon-padding: 5px; 69 | @component-icon-size: 16px; 70 | @component-line-height: 25px; 71 | @component-border-radius: 2px; 72 | 73 | @tab-height: 30px; 74 | 75 | @font-family: Arial; 76 | -------------------------------------------------------------------------------- /spec/ui-watcher-spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const UIWatcher = require('../lib/ui-watcher') 4 | 5 | const {it, fit, ffit, afterEach, beforeEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 6 | 7 | describe('UIWatcher', () => { 8 | let uiWatcher = null 9 | 10 | beforeEach(() => atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'))) 11 | 12 | afterEach(() => uiWatcher && uiWatcher.destroy()) 13 | 14 | describe("when a base theme's file changes", () => { 15 | beforeEach(() => { 16 | spyOn(atom.themes, 'resolveStylesheet').andReturn(path.join(__dirname, 'fixtures', 'static', 'atom.less')) 17 | uiWatcher = new UIWatcher() 18 | }) 19 | 20 | it('reloads all the base styles', () => { 21 | spyOn(atom.themes, 'reloadBaseStylesheets') 22 | 23 | expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(`${path.sep}static${path.sep}`) 24 | 25 | uiWatcher.baseTheme.entities[0].emitter.emit('did-change') 26 | expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() 27 | }) 28 | }) 29 | 30 | it("watches all the style sheets in the theme's styles folder", async () => { 31 | const packagePath = path.join(__dirname, 'fixtures', 'package-with-styles-folder') 32 | 33 | await atom.packages.activatePackage(packagePath) 34 | uiWatcher = new UIWatcher() 35 | 36 | const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1] 37 | 38 | expect(lastWatcher.entities.length).toBe(4) 39 | expect(lastWatcher.entities[0].getPath()).toBe(path.join(packagePath, 'styles')) 40 | expect(lastWatcher.entities[1].getPath()).toBe(path.join(packagePath, 'styles', '3.css')) 41 | expect(lastWatcher.entities[2].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '1.css')) 42 | expect(lastWatcher.entities[3].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '2.less')) 43 | }) 44 | 45 | describe('when a package stylesheet file changes', async () => { 46 | beforeEach(async () => { 47 | await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-manifest')) 48 | uiWatcher = new UIWatcher() 49 | }) 50 | 51 | it('reloads all package styles', () => { 52 | const pack = atom.packages.getActivePackages()[0] 53 | spyOn(pack, 'reloadStylesheets') 54 | 55 | uiWatcher.watchers[uiWatcher.watchers.length - 1].entities[1].emitter.emit('did-change') 56 | 57 | expect(pack.reloadStylesheets).toHaveBeenCalled() 58 | }) 59 | }) 60 | 61 | describe('when a package does not have a stylesheet', () => { 62 | beforeEach(async () => { 63 | await atom.packages.activatePackage('package-with-index') 64 | uiWatcher = new UIWatcher() 65 | }) 66 | 67 | it('does not create a PackageWatcher', () => { 68 | expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined() 69 | }) 70 | }) 71 | 72 | describe('when a package global file changes', () => { 73 | beforeEach(async () => { 74 | atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-multiple-imported-files']) 75 | 76 | await atom.themes.activateThemes() 77 | uiWatcher = new UIWatcher() 78 | }) 79 | 80 | afterEach(() => atom.themes.deactivateThemes()) 81 | 82 | it('reloads every package when the variables file changes', () => { 83 | let varEntity 84 | for (const theme of atom.themes.getActiveThemes()) { 85 | spyOn(theme, 'reloadStylesheets') 86 | } 87 | 88 | for (const entity of uiWatcher.watchedThemes.get('theme-with-multiple-imported-files').entities) { 89 | if (entity.getPath().indexOf('variables') > -1) varEntity = entity 90 | } 91 | varEntity.emitter.emit('did-change') 92 | 93 | for (const theme of atom.themes.getActiveThemes()) { 94 | expect(theme.reloadStylesheets).toHaveBeenCalled() 95 | } 96 | }) 97 | }) 98 | 99 | describe('watcher lifecycle', () => { 100 | it('starts watching a package if it is activated after initial startup', async () => { 101 | uiWatcher = new UIWatcher() 102 | expect(uiWatcher.watchedPackages.size).toBe(0) 103 | 104 | await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) 105 | expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).not.toBeUndefined() 106 | }) 107 | 108 | it('unwatches a package after it is deactivated', async () => { 109 | await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) 110 | uiWatcher = new UIWatcher() 111 | const watcher = uiWatcher.watchedPackages.get('package-with-styles-folder') 112 | expect(watcher).not.toBeUndefined() 113 | 114 | const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy') 115 | watcher.onDidDestroy(watcherDestructionSpy) 116 | 117 | await atom.packages.deactivatePackage('package-with-styles-folder') 118 | expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).toBeUndefined() 119 | expect(uiWatcher.watchedPackages.size).toBe(0) 120 | expect(watcherDestructionSpy).toHaveBeenCalled() 121 | }) 122 | 123 | it('does not watch activated packages after the UI watcher has been destroyed', async () => { 124 | uiWatcher = new UIWatcher() 125 | uiWatcher.destroy() 126 | 127 | await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) 128 | expect(uiWatcher.watchedPackages.size).toBe(0) 129 | }) 130 | }) 131 | 132 | describe('minimal theme packages', () => { 133 | let pack = null 134 | beforeEach(async () => { 135 | atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-index-less']) 136 | await atom.themes.activateThemes() 137 | uiWatcher = new UIWatcher() 138 | pack = atom.themes.getActiveThemes()[0] 139 | }) 140 | 141 | afterEach(() => atom.themes.deactivateThemes()) 142 | 143 | it('watches themes without a styles directory', () => { 144 | spyOn(pack, 'reloadStylesheets') 145 | spyOn(atom.themes, 'reloadBaseStylesheets') 146 | 147 | const watcher = uiWatcher.watchedThemes.get('theme-with-index-less') 148 | 149 | expect(watcher.entities.length).toBe(1) 150 | 151 | watcher.entities[0].emitter.emit('did-change') 152 | expect(pack.reloadStylesheets).toHaveBeenCalled() 153 | expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() 154 | }) 155 | }) 156 | 157 | describe('theme packages', () => { 158 | let pack = null 159 | beforeEach(async () => { 160 | atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-multiple-imported-files']) 161 | 162 | await atom.themes.activateThemes() 163 | uiWatcher = new UIWatcher() 164 | pack = atom.themes.getActiveThemes()[0] 165 | }) 166 | 167 | afterEach(() => atom.themes.deactivateThemes()) 168 | 169 | it('reloads the theme when anything within the theme changes', () => { 170 | spyOn(pack, 'reloadStylesheets') 171 | spyOn(atom.themes, 'reloadBaseStylesheets') 172 | 173 | const watcher = uiWatcher.watchedThemes.get('theme-with-multiple-imported-files') 174 | 175 | expect(watcher.entities.length).toBe(6) 176 | 177 | watcher.entities[2].emitter.emit('did-change') 178 | expect(pack.reloadStylesheets).toHaveBeenCalled() 179 | expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() 180 | 181 | watcher.entities[watcher.entities.length - 1].emitter.emit('did-change') 182 | expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() 183 | }) 184 | 185 | it('unwatches when a theme is deactivated', async () => { 186 | jasmine.useRealClock() 187 | 188 | atom.config.set('core.themes', []) 189 | await conditionPromise(() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files']) 190 | }) 191 | 192 | it('watches a new theme when it is deactivated', async () => { 193 | jasmine.useRealClock() 194 | 195 | atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-package-file']) 196 | await conditionPromise(() => uiWatcher.watchedThemes.get('theme-with-package-file')) 197 | 198 | pack = atom.themes.getActiveThemes()[0] 199 | spyOn(pack, 'reloadStylesheets') 200 | 201 | expect(pack.name).toBe('theme-with-package-file') 202 | 203 | const watcher = uiWatcher.watchedThemes.get('theme-with-package-file') 204 | watcher.entities[2].emitter.emit('did-change') 205 | expect(pack.reloadStylesheets).toHaveBeenCalled() 206 | }) 207 | }) 208 | }) 209 | --------------------------------------------------------------------------------