diff --git a/package-lock.json b/package-lock.json index 17502ab9cc7..08c47e06cb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1769,8 +1769,7 @@ } }, "dev-live-reload": { - "version": "https://www.atom.io/api/packages/dev-live-reload/versions/0.48.1/tarball", - "integrity": "sha512-YSOLkdz7d/pETiG3raCzRKFmv64aErVC2d0cwDi7SLGtJyIGoR9+0OHMZjl8kcXCrX+u1s7awD8HhTDfZ56+iw==", + "version": "file:packages/dev-live-reload", "requires": { "fs-plus": "^3.0.0" } diff --git a/package.json b/package.json index 4911da733eb..70aa89b602a 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "dalek": "https://www.atom.io/api/packages/dalek/versions/0.2.2/tarball", "dedent": "^0.7.0", "deprecation-cop": "https://www.atom.io/api/packages/deprecation-cop/versions/0.56.9/tarball", - "dev-live-reload": "https://www.atom.io/api/packages/dev-live-reload/versions/0.48.1/tarball", + "dev-live-reload": "file:packages/dev-live-reload", "devtron": "1.3.0", "encoding-selector": "https://www.atom.io/api/packages/encoding-selector/versions/0.23.9/tarball", "etch": "^0.12.6", @@ -197,7 +197,7 @@ "command-palette": "0.43.5", "dalek": "0.2.2", "deprecation-cop": "0.56.9", - "dev-live-reload": "0.48.1", + "dev-live-reload": "file:./packages/dev-live-reload", "encoding-selector": "0.23.9", "exception-reporting": "file:./packages/exception-reporting", "find-and-replace": "0.215.14", diff --git a/packages/README.md b/packages/README.md index a6c643a9aba..ccf1081d66a 100644 --- a/packages/README.md +++ b/packages/README.md @@ -28,7 +28,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate | **command-palette** | [`atom/command-palette`][command-palette] | | | **dalek** | [`atom/dalek`][dalek] | [#17838](https://github.com/atom/atom/issues/17838) | | **deprecation-cop** | [`atom/deprecation-cop`][deprecation-cop] | [#17839](https://github.com/atom/atom/issues/17839) | -| **dev-live-reload** | [`atom/dev-live-reload`][dev-live-reload] | [#17840](https://github.com/atom/atom/issues/17840) | +| **dev-live-reload** | [`./dev-live-reload`](dev-live-reload) | [#17840](https://github.com/atom/atom/issues/17840) | | **encoding-selector** | [`atom/encoding-selector`][encoding-selector] | [#17841](https://github.com/atom/atom/issues/17841) | | **exception-reporting** | [`./exception-reporting`](./exception-reporting) | [#17842](https://github.com/atom/atom/issues/17842) | | **find-and-replace** | [`atom/find-and-replace`][find-and-replace] | | @@ -115,7 +115,6 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate [command-palette]: https://github.com/atom/command-palette [dalek]: https://github.com/atom/dalek [deprecation-cop]: https://github.com/atom/deprecation-cop -[dev-live-reload]: https://github.com/atom/dev-live-reload [encoding-selector]: https://github.com/atom/encoding-selector [find-and-replace]: https://github.com/atom/find-and-replace [fuzzy-finder]: https://github.com/atom/fuzzy-finder diff --git a/packages/dev-live-reload/.gitignore b/packages/dev-live-reload/.gitignore new file mode 100644 index 00000000000..3c3629e647f --- /dev/null +++ b/packages/dev-live-reload/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/dev-live-reload/LICENSE.md b/packages/dev-live-reload/LICENSE.md new file mode 100644 index 00000000000..4d231b4563b --- /dev/null +++ b/packages/dev-live-reload/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2014 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/dev-live-reload/README.md b/packages/dev-live-reload/README.md new file mode 100644 index 00000000000..11ef2214a77 --- /dev/null +++ b/packages/dev-live-reload/README.md @@ -0,0 +1,13 @@ +# Dev Live Reload package + +This live reloads the Atom `.less` files. You edit styles and they are magically reflected in any running Atom windows. Magic! :tophat: :sparkles: :rabbit2: + +Installed by default on Atom windows running in dev mode. Use the "Application: Open Dev" command to open a new dev mode window. + +Use meta-shift-ctrl-r to reload all core and package stylesheets. + +This package is __experimental__, it does not handle the following: + +* File additions to a theme. New files will not be watched. + +![gif](https://f.cloud.github.com/assets/69169/1387004/d2dc45f2-3b84-11e3-877e-cac8c51e9702.gif) diff --git a/packages/dev-live-reload/keymaps/dev-live-reload.cson b/packages/dev-live-reload/keymaps/dev-live-reload.cson new file mode 100644 index 00000000000..dc6a456f38a --- /dev/null +++ b/packages/dev-live-reload/keymaps/dev-live-reload.cson @@ -0,0 +1,5 @@ +'.platform-darwin': + 'cmd-ctrl-R': 'dev-live-reload:reload-all' + +'.platform-win32': + 'alt-ctrl-R': 'dev-live-reload:reload-all' diff --git a/packages/dev-live-reload/lib/base-theme-watcher.js b/packages/dev-live-reload/lib/base-theme-watcher.js new file mode 100644 index 00000000000..8d8658a8cf5 --- /dev/null +++ b/packages/dev-live-reload/lib/base-theme-watcher.js @@ -0,0 +1,28 @@ +const fs = require('fs-plus') +const path = require('path') +const Watcher = require('./watcher') + +module.exports = +class BaseThemeWatcher extends Watcher { + constructor () { + super() + this.stylesheetsPath = path.dirname(atom.themes.resolveStylesheet('../static/atom.less')) + this.watch() + } + + watch () { + const filePaths = fs.readdirSync(this.stylesheetsPath).filter(filePath => path.extname(filePath).includes('less')) + + for (const filePath of filePaths) { + this.watchFile(path.join(this.stylesheetsPath, filePath)) + } + } + + loadStylesheet () { + this.loadAllStylesheets() + } + + loadAllStylesheets () { + atom.themes.reloadBaseStylesheets() + } +} diff --git a/packages/dev-live-reload/lib/main.js b/packages/dev-live-reload/lib/main.js new file mode 100644 index 00000000000..8e80de68f50 --- /dev/null +++ b/packages/dev-live-reload/lib/main.js @@ -0,0 +1,24 @@ +module.exports = { + activate (state) { + if (!atom.inDevMode() || atom.inSpecMode()) return + + if (atom.packages.hasActivatedInitialPackages()) { + this.startWatching() + } else { + this.activatedDisposable = atom.packages.onDidActivateInitialPackages(() => this.startWatching()) + } + }, + + deactivate () { + if (this.activatedDisposable) this.activatedDisposable.dispose() + if (this.commandDisposable) this.commandDisposable.dispose() + if (this.uiWatcher) this.uiWatcher.destroy() + }, + + startWatching () { + const UIWatcher = require('./ui-watcher') + this.uiWatcher = new UIWatcher({themeManager: atom.themes}) + this.commandDisposable = atom.commands.add('atom-workspace', 'dev-live-reload:reload-all', () => this.uiWatcher.reloadAll()) + if (this.activatedDisposable) this.activatedDisposable.dispose() + } +} diff --git a/packages/dev-live-reload/lib/package-watcher.js b/packages/dev-live-reload/lib/package-watcher.js new file mode 100644 index 00000000000..044b59719b2 --- /dev/null +++ b/packages/dev-live-reload/lib/package-watcher.js @@ -0,0 +1,48 @@ +const fs = require('fs-plus') + +const Watcher = require('./watcher') + +module.exports = +class PackageWatcher extends Watcher { + static supportsPackage (pack, type) { + if (pack.getType() === type && pack.getStylesheetPaths().length) return true + return false + } + + constructor (pack) { + super() + this.pack = pack + this.watch() + } + + watch () { + const watchedPaths = [] + const watchPath = stylesheet => { + if (!watchedPaths.includes(stylesheet)) this.watchFile(stylesheet) + watchedPaths.push(stylesheet) + } + + const stylesheetsPath = this.pack.getStylesheetsPath() + + if (fs.isDirectorySync(stylesheetsPath)) this.watchDirectory(stylesheetsPath) + + const stylesheetPaths = new Set(this.pack.getStylesheetPaths()) + const onFile = stylesheetPath => stylesheetPaths.add(stylesheetPath) + const onFolder = () => true + fs.traverseTreeSync(stylesheetsPath, onFile, onFolder) + + for (let stylesheet of stylesheetPaths) { + watchPath(stylesheet) + } + } + + loadStylesheet (pathName) { + if (pathName.includes('variables')) this.emitGlobalsChanged() + this.loadAllStylesheets() + } + + loadAllStylesheets () { + console.log('Reloading package', this.pack.name) + this.pack.reloadStylesheets() + } +} diff --git a/packages/dev-live-reload/lib/ui-watcher.js b/packages/dev-live-reload/lib/ui-watcher.js new file mode 100644 index 00000000000..458d31a78da --- /dev/null +++ b/packages/dev-live-reload/lib/ui-watcher.js @@ -0,0 +1,81 @@ +const {CompositeDisposable} = require('atom') + +const BaseThemeWatcher = require('./base-theme-watcher') +const PackageWatcher = require('./package-watcher') + +module.exports = +class UIWatcher { + constructor () { + this.subscriptions = new CompositeDisposable() + this.reloadAll = this.reloadAll.bind(this) + this.watchers = [] + this.baseTheme = this.createWatcher(new BaseThemeWatcher()) + this.watchPackages() + } + + watchPackages () { + this.watchedThemes = new Map() + this.watchedPackages = new Map() + for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) } + for (const pack of atom.packages.getActivePackages()) { this.watchPackage(pack) } + this.watchForPackageChanges() + } + + watchForPackageChanges () { + this.subscriptions.add(atom.themes.onDidChangeActiveThemes(() => { + // We need to destroy all theme watchers as all theme packages are destroyed + // when a theme changes. + for (const theme of this.watchedThemes.values()) { theme.destroy() } + + this.watchedThemes.clear() + + // Rewatch everything! + for (const theme of atom.themes.getActiveThemes()) { this.watchTheme(theme) } + })) + + this.subscriptions.add(atom.packages.onDidActivatePackage(pack => this.watchPackage(pack))) + + this.subscriptions.add(atom.packages.onDidDeactivatePackage(pack => { + // This only handles packages - onDidChangeActiveThemes handles themes + const watcher = this.watchedPackages.get(pack.name) + if (watcher) watcher.destroy() + this.watchedPackages.delete(pack.name) + })) + } + + watchTheme (theme) { + if (PackageWatcher.supportsPackage(theme, 'theme')) this.watchedThemes.set(theme.name, this.createWatcher(new PackageWatcher(theme))) + } + + watchPackage (pack) { + if (PackageWatcher.supportsPackage(pack, 'atom')) this.watchedPackages.set(pack.name, this.createWatcher(new PackageWatcher(pack))) + } + + createWatcher (watcher) { + watcher.onDidChangeGlobals(() => { + console.log('Global changed, reloading all styles') + this.reloadAll() + }) + watcher.onDidDestroy(() => this.watchers.splice(this.watchers.indexOf(watcher), 1)) + this.watchers.push(watcher) + return watcher + } + + reloadAll () { + this.baseTheme.loadAllStylesheets() + for (const pack of atom.packages.getActivePackages()) { + if (PackageWatcher.supportsPackage(pack, 'atom')) pack.reloadStylesheets() + } + + for (const theme of atom.themes.getActiveThemes()) { + if (PackageWatcher.supportsPackage(theme, 'theme')) theme.reloadStylesheets() + } + } + + destroy () { + this.subscriptions.dispose() + this.baseTheme.destroy() + for (const pack of this.watchedPackages.values()) { pack.destroy() } + for (const theme of this.watchedThemes.values()) { theme.destroy() } + } +} diff --git a/packages/dev-live-reload/lib/watcher.js b/packages/dev-live-reload/lib/watcher.js new file mode 100644 index 00000000000..2ecde1eeffc --- /dev/null +++ b/packages/dev-live-reload/lib/watcher.js @@ -0,0 +1,72 @@ +const {CompositeDisposable, File, Directory, Emitter} = require('atom') +const path = require('path') + +module.exports = +class Watcher { + constructor () { + this.destroy = this.destroy.bind(this) + this.emitter = new Emitter() + this.disposables = new CompositeDisposable() + this.entities = [] // Used for specs + } + + onDidDestroy (callback) { + this.emitter.on('did-destroy', callback) + } + + onDidChangeGlobals (callback) { + this.emitter.on('did-change-globals', callback) + } + + destroy () { + this.disposables.dispose() + this.entities = null + this.emitter.emit('did-destroy') + this.emitter.dispose() + } + + watch () { + // override me + } + + loadStylesheet (stylesheetPath) { + // override me + } + + loadAllStylesheets () { + // override me + } + + emitGlobalsChanged () { + this.emitter.emit('did-change-globals') + } + + watchDirectory (directoryPath) { + if (this.isInAsarArchive(directoryPath)) return + const entity = new Directory(directoryPath) + this.disposables.add(entity.onDidChange(() => this.loadAllStylesheets())) + this.entities.push(entity) + } + + watchGlobalFile (filePath) { + const entity = new File(filePath) + this.disposables.add(entity.onDidChange(() => this.emitGlobalsChanged())) + this.entities.push(entity) + } + + watchFile (filePath) { + if (this.isInAsarArchive(filePath)) return + const reloadFn = () => this.loadStylesheet(entity.getPath()) + + const entity = new File(filePath) + this.disposables.add(entity.onDidChange(reloadFn)) + this.disposables.add(entity.onDidDelete(reloadFn)) + this.disposables.add(entity.onDidRename(reloadFn)) + this.entities.push(entity) + } + + isInAsarArchive (pathToCheck) { + const {resourcePath} = atom.getLoadSettings() + return pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar' + } +} diff --git a/packages/dev-live-reload/menus/dev-live-reload.cson b/packages/dev-live-reload/menus/dev-live-reload.cson new file mode 100644 index 00000000000..b4e21f4262d --- /dev/null +++ b/packages/dev-live-reload/menus/dev-live-reload.cson @@ -0,0 +1,9 @@ +'menu': [ + 'label': 'Packages' + 'submenu': [ + 'label': 'Dev Live Reload' + 'submenu': [ + { 'label': 'Reload All Styles', 'command': 'dev-live-reload:reload-all' } + ] + ] +] diff --git a/packages/dev-live-reload/package.json b/packages/dev-live-reload/package.json new file mode 100644 index 00000000000..9307cd490b6 --- /dev/null +++ b/packages/dev-live-reload/package.json @@ -0,0 +1,28 @@ +{ + "name": "dev-live-reload", + "main": "./lib/main", + "version": "0.48.1", + "description": "Live reload atom themes and packages.", + "repository": "https://github.com/atom/atom", + "license": "MIT", + "dependencies": { + "fs-plus": "^3.0.0" + }, + "engines": { + "atom": "*" + }, + "devDependencies": { + "standard": "^10.0.3" + }, + "standard": { + "env": { + "atomtest": true, + "browser": true, + "jasmine": true, + "node": true + }, + "globals": [ + "atom" + ] + } +} diff --git a/packages/dev-live-reload/spec/async-spec-helpers.js b/packages/dev-live-reload/spec/async-spec-helpers.js new file mode 100644 index 00000000000..73002c049a4 --- /dev/null +++ b/packages/dev-live-reload/spec/async-spec-helpers.js @@ -0,0 +1,103 @@ +/** @babel */ + +export function beforeEach (fn) { + global.beforeEach(function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(() => result) + } + }) +} + +export function afterEach (fn) { + global.afterEach(function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(() => result) + } + }) +} + +['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { + module.exports[name] = function (description, fn) { + if (fn === undefined) { + global[name](description) + return + } + + global[name](description, function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(() => result) + } + }) + } +}) + +export async function conditionPromise (condition, description = 'anonymous condition') { + const startTime = Date.now() + + while (true) { + await timeoutPromise(100) + + if (await condition()) { + return + } + + if (Date.now() - startTime > 5000) { + throw new Error('Timed out waiting on ' + description) + } + } +} + +export function timeoutPromise (timeout) { + return new Promise(function (resolve) { + global.setTimeout(resolve, timeout) + }) +} + +function waitsForPromise (fn) { + const promise = fn() + global.waitsFor('spec promise to resolve', function (done) { + promise.then(done, function (error) { + jasmine.getEnv().currentSpec.fail(error) + done() + }) + }) +} + +export function emitterEventPromise (emitter, event, timeout = 15000) { + return new Promise((resolve, reject) => { + const timeoutHandle = setTimeout(() => { + reject(new Error(`Timed out waiting for '${event}' event`)) + }, timeout) + emitter.once(event, () => { + clearTimeout(timeoutHandle) + resolve() + }) + }) +} + +export function promisify (original) { + return function (...args) { + return new Promise((resolve, reject) => { + args.push((err, ...results) => { + if (err) { + reject(err) + } else { + resolve(...results) + } + }) + + return original(...args) + }) + } +} + +export function promisifySome (obj, fnNames) { + const result = {} + for (const fnName of fnNames) { + result[fnName] = promisify(obj[fnName]) + } + return result +} diff --git a/packages/dev-live-reload/spec/dev-live-reload-spec.js b/packages/dev-live-reload/spec/dev-live-reload-spec.js new file mode 100644 index 00000000000..dec828b65de --- /dev/null +++ b/packages/dev-live-reload/spec/dev-live-reload-spec.js @@ -0,0 +1,124 @@ +const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars + +describe('Dev Live Reload', () => { + describe('package activation', () => { + let [pack, mainModule] = [] + + beforeEach(() => { + pack = atom.packages.loadPackage('dev-live-reload') + pack.requireMainModule() + mainModule = pack.mainModule + spyOn(mainModule, 'startWatching') + }) + + describe('when the window is not in dev mode', () => { + beforeEach(() => spyOn(atom, 'inDevMode').andReturn(false)) + + it('does not watch files', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + + await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.startWatching).not.toHaveBeenCalled() + }) + }) + + describe('when the window is in spec mode', () => { + beforeEach(() => spyOn(atom, 'inSpecMode').andReturn(true)) + + it('does not watch files', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + + await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.startWatching).not.toHaveBeenCalled() + }) + }) + + describe('when the window is in dev mode', () => { + beforeEach(() => { + spyOn(atom, 'inDevMode').andReturn(true) + spyOn(atom, 'inSpecMode').andReturn(false) + }) + + it('watches files', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + + await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.startWatching).toHaveBeenCalled() + }) + }) + + describe('when the window is in both dev mode and spec mode', () => { + beforeEach(() => { + spyOn(atom, 'inDevMode').andReturn(true) + spyOn(atom, 'inSpecMode').andReturn(true) + }) + + it('does not watch files', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + + await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.startWatching).not.toHaveBeenCalled() + }) + }) + + describe('when the package is activated before initial packages have been activated', () => { + beforeEach(() => { + spyOn(atom, 'inDevMode').andReturn(true) + spyOn(atom, 'inSpecMode').andReturn(false) + }) + + it('waits until all initial packages have been activated before watching files', async () => { + await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.startWatching).not.toHaveBeenCalled() + + atom.packages.emitter.emit('did-activate-initial-packages') + expect(mainModule.startWatching).toHaveBeenCalled() + }) + }) + }) + + describe('package deactivation', () => { + beforeEach(() => { + spyOn(atom, 'inDevMode').andReturn(true) + spyOn(atom, 'inSpecMode').andReturn(false) + }) + + it('stops watching all files', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + const {mainModule} = await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.uiWatcher).not.toBeNull() + + spyOn(mainModule.uiWatcher, 'destroy') + + await atom.packages.deactivatePackage('dev-live-reload') + expect(mainModule.uiWatcher.destroy).toHaveBeenCalled() + }) + + it('unsubscribes from the onDidActivateInitialPackages subscription if it is disabled before all initial packages are activated', async () => { + const {mainModule} = await atom.packages.activatePackage('dev-live-reload') + expect(mainModule.activatedDisposable.disposed).toBe(false) + + await atom.packages.deactivatePackage('dev-live-reload') + expect(mainModule.activatedDisposable.disposed).toBe(true) + + spyOn(mainModule, 'startWatching') + atom.packages.emitter.emit('did-activate-initial-packages') + expect(mainModule.startWatching).not.toHaveBeenCalled() + }) + + it('removes its commands', async () => { + spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) + await atom.packages.activatePackage('dev-live-reload') + expect(atom.commands + .findCommands({target: atom.views.getView(atom.workspace)}) + .filter(command => command.name.startsWith('dev-live-reload')) + .length).toBeGreaterThan(0) + + await atom.packages.deactivatePackage('dev-live-reload') + expect(atom.commands + .findCommands({target: atom.views.getView(atom.workspace)}) + .filter(command => command.name.startsWith('dev-live-reload')) + .length).toBe(0) + }) + }) +}) diff --git a/packages/dev-live-reload/spec/fixtures/package-with-index/index.coffee b/packages/dev-live-reload/spec/fixtures/package-with-index/index.coffee new file mode 100644 index 00000000000..825eb405575 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-index/index.coffee @@ -0,0 +1,2 @@ +module.exports = +activate: -> diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/package.cson b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/package.cson new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/package.cson @@ -0,0 +1 @@ +{} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/3.css b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/3.css new file mode 100644 index 00000000000..f43408c63f3 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/3.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 3px; +} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/1.css b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/1.css new file mode 100644 index 00000000000..c16849c0e23 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/1.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 1px; +} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/2.less b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/2.less new file mode 100644 index 00000000000..c0ffbc89703 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-folder/styles/sub/2.less @@ -0,0 +1,5 @@ +@size: 2px; + +#jasmine-content { + font-size: @size; +} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/package.cson b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/package.cson new file mode 100644 index 00000000000..07c6082e57b --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/package.cson @@ -0,0 +1 @@ +styleSheets: ['2', '1'] diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/1.css b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/1.css new file mode 100644 index 00000000000..c16849c0e23 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/1.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 1px; +} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/2.less b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/2.less new file mode 100644 index 00000000000..c0ffbc89703 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/2.less @@ -0,0 +1,5 @@ +@size: 2px; + +#jasmine-content { + font-size: @size; +} diff --git a/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/3.css b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/3.css new file mode 100644 index 00000000000..f43408c63f3 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/package-with-styles-manifest/styles/3.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 3px; +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/index.less b/packages/dev-live-reload/spec/fixtures/packages/index.less new file mode 100644 index 00000000000..fd17953b9b5 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/index.less @@ -0,0 +1,3 @@ +@import "styles/first"; +@import "styles/second"; +@import "styles/last"; diff --git a/packages/dev-live-reload/spec/fixtures/packages/package.cson b/packages/dev-live-reload/spec/fixtures/packages/package.cson new file mode 100644 index 00000000000..07c6082e57b --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/package.cson @@ -0,0 +1 @@ +styleSheets: ['2', '1'] diff --git a/packages/dev-live-reload/spec/fixtures/packages/package.json b/packages/dev-live-reload/spec/fixtures/packages/package.json new file mode 100644 index 00000000000..a4dc0188d5a --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/package.json @@ -0,0 +1,3 @@ +{ + "theme": true +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/1.css b/packages/dev-live-reload/spec/fixtures/packages/styles/1.css new file mode 100644 index 00000000000..c16849c0e23 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/1.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 1px; +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/2.less b/packages/dev-live-reload/spec/fixtures/packages/styles/2.less new file mode 100644 index 00000000000..c0ffbc89703 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/2.less @@ -0,0 +1,5 @@ +@size: 2px; + +#jasmine-content { + font-size: @size; +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/3.css b/packages/dev-live-reload/spec/fixtures/packages/styles/3.css new file mode 100644 index 00000000000..f43408c63f3 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/3.css @@ -0,0 +1,3 @@ +#jasmine-content { + font-size: 3px; +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/first.less b/packages/dev-live-reload/spec/fixtures/packages/styles/first.less new file mode 100644 index 00000000000..f9af1a34556 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/first.less @@ -0,0 +1,7 @@ +.editor { + padding-top: 101px; + padding-right: 101px; + padding-bottom: 101px; + + color: red; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/last.less b/packages/dev-live-reload/spec/fixtures/packages/styles/last.less new file mode 100644 index 00000000000..c0cface8c4e --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/last.less @@ -0,0 +1,5 @@ +.editor { +/* padding-top: 103px; + padding-right: 103px;*/ + padding-bottom: 103px; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/second.less b/packages/dev-live-reload/spec/fixtures/packages/styles/second.less new file mode 100644 index 00000000000..a14760b4f35 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/second.less @@ -0,0 +1,9 @@ +@import "ui-variables"; + +@number: 102px; + +.editor { +/* padding-top: 102px;*/ + padding-right: @number; + padding-bottom: @number; +} diff --git a/packages/dev-live-reload/spec/fixtures/packages/styles/ui-variables.less b/packages/dev-live-reload/spec/fixtures/packages/styles/ui-variables.less new file mode 100644 index 00000000000..9cefd68235e --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/packages/styles/ui-variables.less @@ -0,0 +1,75 @@ +// Variables different from the original are marked 'Changed' + +@text-color: #333; +@text-color-subtle: #777; +@text-color-highlight: #111; +@text-color-selected: @text-color-highlight; + +@text-color-info: #5293d8; +@text-color-success: #1fe977; +@text-color-warning: #f78a46; +@text-color-error: #c00; + +@background-color-info: #0098ff; +@background-color-success: #17ca65; +@background-color-warning: #ff4800; +@background-color-error: #c00; +@background-color-highlight: rgba(255, 255, 255, 0.10); +@background-color-selected: @background-color-highlight; + +@app-background-color: #00f; // Changed + +@base-background-color: #fff; +@base-border-color: #eee; + +@pane-item-background-color: @base-background-color; +@pane-item-border-color: @base-border-color; + +@input-background-color: #f00; // Changed +@input-border-color: @base-border-color; + +@tool-panel-background-color: #f4f4f4; +@tool-panel-border-color: @base-border-color; + +@inset-panel-background-color: #eee; +@inset-panel-border-color: @base-border-color; + +@panel-heading-background-color: #ddd; +@panel-heading-border-color: transparent; + +@overlay-background-color: #f4f4f4; +@overlay-border-color: @base-border-color; + +@button-background-color: #ccc; +@button-background-color-hover: lighten(@button-background-color, 5%); +@button-background-color-selected: @button-background-color-hover; +@button-border-color: #aaa; + +@tab-bar-background-color: #fff; +@tab-bar-border-color: darken(@tab-background-color-active, 10%); +@tab-background-color: #f4f4f4; +@tab-background-color-active: #fff; +@tab-border-color: @base-border-color; + +@tree-view-background-color: @tool-panel-background-color; +@tree-view-border-color: @tool-panel-border-color; + +@ui-site-color-1: @background-color-success; // green +@ui-site-color-2: @background-color-info; // blue +@ui-site-color-3: @background-color-warning; // orange +@ui-site-color-4: #db2ff4; // purple +@ui-site-color-5: #f5e11d; // yellow + +@font-size: 12px; + +@disclosure-arrow-size: 12px; + +@component-padding: 150px; +@component-icon-padding: 5px; +@component-icon-size: 16px; +@component-line-height: 25px; +@component-border-radius: 2px; + +@tab-height: 30px; + +@font-family: Arial; diff --git a/packages/dev-live-reload/spec/fixtures/static/atom.less b/packages/dev-live-reload/spec/fixtures/static/atom.less new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-index-less/index.less b/packages/dev-live-reload/spec/fixtures/theme-with-index-less/index.less new file mode 100644 index 00000000000..79ce1b2fffb --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-index-less/index.less @@ -0,0 +1,5 @@ +@padding: 4321px; + +atom-text-editor { + padding-top: @padding; +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-index-less/package.json b/packages/dev-live-reload/spec/fixtures/theme-with-index-less/package.json new file mode 100644 index 00000000000..16e770b0568 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-index-less/package.json @@ -0,0 +1,3 @@ +{ + "theme": "ui" +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/index.less b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/index.less new file mode 100644 index 00000000000..fd17953b9b5 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/index.less @@ -0,0 +1,3 @@ +@import "styles/first"; +@import "styles/second"; +@import "styles/last"; diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/package.json b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/package.json new file mode 100644 index 00000000000..dbc7f35ada6 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/package.json @@ -0,0 +1,4 @@ +{ + "name": "theme-with-multiple-imported-files", + "theme": "ui" +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/first.less b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/first.less new file mode 100644 index 00000000000..f9af1a34556 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/first.less @@ -0,0 +1,7 @@ +.editor { + padding-top: 101px; + padding-right: 101px; + padding-bottom: 101px; + + color: red; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/last.less b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/last.less new file mode 100644 index 00000000000..c0cface8c4e --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/last.less @@ -0,0 +1,5 @@ +.editor { +/* padding-top: 103px; + padding-right: 103px;*/ + padding-bottom: 103px; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/second.less b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/second.less new file mode 100644 index 00000000000..a14760b4f35 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/second.less @@ -0,0 +1,9 @@ +@import "ui-variables"; + +@number: 102px; + +.editor { +/* padding-top: 102px;*/ + padding-right: @number; + padding-bottom: @number; +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/ui-variables.less b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/ui-variables.less new file mode 100644 index 00000000000..9cefd68235e --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-multiple-imported-files/styles/ui-variables.less @@ -0,0 +1,75 @@ +// Variables different from the original are marked 'Changed' + +@text-color: #333; +@text-color-subtle: #777; +@text-color-highlight: #111; +@text-color-selected: @text-color-highlight; + +@text-color-info: #5293d8; +@text-color-success: #1fe977; +@text-color-warning: #f78a46; +@text-color-error: #c00; + +@background-color-info: #0098ff; +@background-color-success: #17ca65; +@background-color-warning: #ff4800; +@background-color-error: #c00; +@background-color-highlight: rgba(255, 255, 255, 0.10); +@background-color-selected: @background-color-highlight; + +@app-background-color: #00f; // Changed + +@base-background-color: #fff; +@base-border-color: #eee; + +@pane-item-background-color: @base-background-color; +@pane-item-border-color: @base-border-color; + +@input-background-color: #f00; // Changed +@input-border-color: @base-border-color; + +@tool-panel-background-color: #f4f4f4; +@tool-panel-border-color: @base-border-color; + +@inset-panel-background-color: #eee; +@inset-panel-border-color: @base-border-color; + +@panel-heading-background-color: #ddd; +@panel-heading-border-color: transparent; + +@overlay-background-color: #f4f4f4; +@overlay-border-color: @base-border-color; + +@button-background-color: #ccc; +@button-background-color-hover: lighten(@button-background-color, 5%); +@button-background-color-selected: @button-background-color-hover; +@button-border-color: #aaa; + +@tab-bar-background-color: #fff; +@tab-bar-border-color: darken(@tab-background-color-active, 10%); +@tab-background-color: #f4f4f4; +@tab-background-color-active: #fff; +@tab-border-color: @base-border-color; + +@tree-view-background-color: @tool-panel-background-color; +@tree-view-border-color: @tool-panel-border-color; + +@ui-site-color-1: @background-color-success; // green +@ui-site-color-2: @background-color-info; // blue +@ui-site-color-3: @background-color-warning; // orange +@ui-site-color-4: #db2ff4; // purple +@ui-site-color-5: #f5e11d; // yellow + +@font-size: 12px; + +@disclosure-arrow-size: 12px; + +@component-padding: 150px; +@component-icon-padding: 5px; +@component-icon-size: 16px; +@component-line-height: 25px; +@component-border-radius: 2px; + +@tab-height: 30px; + +@font-family: Arial; diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-package-file/package.json b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/package.json new file mode 100644 index 00000000000..71ec9577a9a --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/package.json @@ -0,0 +1,4 @@ +{ + "theme": "ui", + "styleSheets": ["first.css", "second.less", "last.css"] +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/first.css b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/first.css new file mode 100644 index 00000000000..75b58e9cfa7 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/first.css @@ -0,0 +1,7 @@ +atom-text-editor { + padding-top: 101px; + padding-right: 101px; + padding-bottom: 101px; + + color: red; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/last.css b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/last.css new file mode 100644 index 00000000000..ceb1a3e470c --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/last.css @@ -0,0 +1,5 @@ +atom-text-editor { +/* padding-top: 103px; + padding-right: 103px;*/ + padding-bottom: 103px; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/second.less b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/second.less new file mode 100644 index 00000000000..090f1872c52 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-package-file/styles/second.less @@ -0,0 +1,7 @@ +@number: 102px; + +atom-text-editor { +/* padding-top: 102px;*/ + padding-right: @number; + padding-bottom: @number; +} \ No newline at end of file diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-syntax-variables/package.json b/packages/dev-live-reload/spec/fixtures/theme-with-syntax-variables/package.json new file mode 100644 index 00000000000..7f8bafacb03 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-syntax-variables/package.json @@ -0,0 +1,4 @@ +{ + "theme": "syntax", + "styleSheets": ["editor.less"] +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-syntax-variables/styles/editor.less b/packages/dev-live-reload/spec/fixtures/theme-with-syntax-variables/styles/editor.less new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/package.json b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/package.json new file mode 100644 index 00000000000..b62d9ab61e5 --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/package.json @@ -0,0 +1,4 @@ +{ + "theme": "ui", + "styleSheets": ["editor.less"] +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/editor.less b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/editor.less new file mode 100644 index 00000000000..8f32addd38b --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/editor.less @@ -0,0 +1,9 @@ +@import "ui-variables"; + +atom-text-editor { + padding-top: @component-padding; + padding-right: @component-padding; + padding-bottom: @component-padding; + + color: @input-background-color; +} diff --git a/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/ui-variables.less b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/ui-variables.less new file mode 100644 index 00000000000..9cefd68235e --- /dev/null +++ b/packages/dev-live-reload/spec/fixtures/theme-with-ui-variables/styles/ui-variables.less @@ -0,0 +1,75 @@ +// Variables different from the original are marked 'Changed' + +@text-color: #333; +@text-color-subtle: #777; +@text-color-highlight: #111; +@text-color-selected: @text-color-highlight; + +@text-color-info: #5293d8; +@text-color-success: #1fe977; +@text-color-warning: #f78a46; +@text-color-error: #c00; + +@background-color-info: #0098ff; +@background-color-success: #17ca65; +@background-color-warning: #ff4800; +@background-color-error: #c00; +@background-color-highlight: rgba(255, 255, 255, 0.10); +@background-color-selected: @background-color-highlight; + +@app-background-color: #00f; // Changed + +@base-background-color: #fff; +@base-border-color: #eee; + +@pane-item-background-color: @base-background-color; +@pane-item-border-color: @base-border-color; + +@input-background-color: #f00; // Changed +@input-border-color: @base-border-color; + +@tool-panel-background-color: #f4f4f4; +@tool-panel-border-color: @base-border-color; + +@inset-panel-background-color: #eee; +@inset-panel-border-color: @base-border-color; + +@panel-heading-background-color: #ddd; +@panel-heading-border-color: transparent; + +@overlay-background-color: #f4f4f4; +@overlay-border-color: @base-border-color; + +@button-background-color: #ccc; +@button-background-color-hover: lighten(@button-background-color, 5%); +@button-background-color-selected: @button-background-color-hover; +@button-border-color: #aaa; + +@tab-bar-background-color: #fff; +@tab-bar-border-color: darken(@tab-background-color-active, 10%); +@tab-background-color: #f4f4f4; +@tab-background-color-active: #fff; +@tab-border-color: @base-border-color; + +@tree-view-background-color: @tool-panel-background-color; +@tree-view-border-color: @tool-panel-border-color; + +@ui-site-color-1: @background-color-success; // green +@ui-site-color-2: @background-color-info; // blue +@ui-site-color-3: @background-color-warning; // orange +@ui-site-color-4: #db2ff4; // purple +@ui-site-color-5: #f5e11d; // yellow + +@font-size: 12px; + +@disclosure-arrow-size: 12px; + +@component-padding: 150px; +@component-icon-padding: 5px; +@component-icon-size: 16px; +@component-line-height: 25px; +@component-border-radius: 2px; + +@tab-height: 30px; + +@font-family: Arial; diff --git a/packages/dev-live-reload/spec/ui-watcher-spec.js b/packages/dev-live-reload/spec/ui-watcher-spec.js new file mode 100644 index 00000000000..c52ce35ab40 --- /dev/null +++ b/packages/dev-live-reload/spec/ui-watcher-spec.js @@ -0,0 +1,208 @@ +const path = require('path') + +const UIWatcher = require('../lib/ui-watcher') + +const {it, fit, ffit, afterEach, beforeEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars + +describe('UIWatcher', () => { + let uiWatcher = null + + beforeEach(() => atom.packages.packageDirPaths.push(path.join(__dirname, 'fixtures'))) + + afterEach(() => uiWatcher && uiWatcher.destroy()) + + describe("when a base theme's file changes", () => { + beforeEach(() => { + spyOn(atom.themes, 'resolveStylesheet').andReturn(path.join(__dirname, 'fixtures', 'static', 'atom.less')) + uiWatcher = new UIWatcher() + }) + + it('reloads all the base styles', () => { + spyOn(atom.themes, 'reloadBaseStylesheets') + + expect(uiWatcher.baseTheme.entities[0].getPath()).toContain(`${path.sep}static${path.sep}`) + + uiWatcher.baseTheme.entities[0].emitter.emit('did-change') + expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() + }) + }) + + it("watches all the style sheets in the theme's styles folder", async () => { + const packagePath = path.join(__dirname, 'fixtures', 'package-with-styles-folder') + + await atom.packages.activatePackage(packagePath) + uiWatcher = new UIWatcher() + + const lastWatcher = uiWatcher.watchers[uiWatcher.watchers.length - 1] + + expect(lastWatcher.entities.length).toBe(4) + expect(lastWatcher.entities[0].getPath()).toBe(path.join(packagePath, 'styles')) + expect(lastWatcher.entities[1].getPath()).toBe(path.join(packagePath, 'styles', '3.css')) + expect(lastWatcher.entities[2].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '1.css')) + expect(lastWatcher.entities[3].getPath()).toBe(path.join(packagePath, 'styles', 'sub', '2.less')) + }) + + describe('when a package stylesheet file changes', async () => { + beforeEach(async () => { + await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-manifest')) + uiWatcher = new UIWatcher() + }) + + it('reloads all package styles', () => { + const pack = atom.packages.getActivePackages()[0] + spyOn(pack, 'reloadStylesheets') + + uiWatcher.watchers[uiWatcher.watchers.length - 1].entities[1].emitter.emit('did-change') + + expect(pack.reloadStylesheets).toHaveBeenCalled() + }) + }) + + describe('when a package does not have a stylesheet', () => { + beforeEach(async () => { + await atom.packages.activatePackage('package-with-index') + uiWatcher = new UIWatcher() + }) + + it('does not create a PackageWatcher', () => { + expect(uiWatcher.watchedPackages['package-with-index']).toBeUndefined() + }) + }) + + describe('when a package global file changes', () => { + beforeEach(async () => { + atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-multiple-imported-files']) + + await atom.themes.activateThemes() + uiWatcher = new UIWatcher() + }) + + afterEach(() => atom.themes.deactivateThemes()) + + it('reloads every package when the variables file changes', () => { + let varEntity + for (const theme of atom.themes.getActiveThemes()) { + spyOn(theme, 'reloadStylesheets') + } + + for (const entity of uiWatcher.watchedThemes.get('theme-with-multiple-imported-files').entities) { + if (entity.getPath().indexOf('variables') > -1) varEntity = entity + } + varEntity.emitter.emit('did-change') + + for (const theme of atom.themes.getActiveThemes()) { + expect(theme.reloadStylesheets).toHaveBeenCalled() + } + }) + }) + + describe('watcher lifecycle', () => { + it('starts watching a package if it is activated after initial startup', async () => { + uiWatcher = new UIWatcher() + expect(uiWatcher.watchedPackages.size).toBe(0) + + await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) + expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).not.toBeUndefined() + }) + + it('unwatches a package after it is deactivated', async () => { + await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) + uiWatcher = new UIWatcher() + const watcher = uiWatcher.watchedPackages.get('package-with-styles-folder') + expect(watcher).not.toBeUndefined() + + const watcherDestructionSpy = jasmine.createSpy('watcher-on-did-destroy') + watcher.onDidDestroy(watcherDestructionSpy) + + await atom.packages.deactivatePackage('package-with-styles-folder') + expect(uiWatcher.watchedPackages.get('package-with-styles-folder')).toBeUndefined() + expect(uiWatcher.watchedPackages.size).toBe(0) + expect(watcherDestructionSpy).toHaveBeenCalled() + }) + + it('does not watch activated packages after the UI watcher has been destroyed', async () => { + uiWatcher = new UIWatcher() + uiWatcher.destroy() + + await atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'package-with-styles-folder')) + expect(uiWatcher.watchedPackages.size).toBe(0) + }) + }) + + describe('minimal theme packages', () => { + let pack = null + beforeEach(async () => { + atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-index-less']) + await atom.themes.activateThemes() + uiWatcher = new UIWatcher() + pack = atom.themes.getActiveThemes()[0] + }) + + afterEach(() => atom.themes.deactivateThemes()) + + it('watches themes without a styles directory', () => { + spyOn(pack, 'reloadStylesheets') + spyOn(atom.themes, 'reloadBaseStylesheets') + + const watcher = uiWatcher.watchedThemes.get('theme-with-index-less') + + expect(watcher.entities.length).toBe(1) + + watcher.entities[0].emitter.emit('did-change') + expect(pack.reloadStylesheets).toHaveBeenCalled() + expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() + }) + }) + + describe('theme packages', () => { + let pack = null + beforeEach(async () => { + atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-multiple-imported-files']) + + await atom.themes.activateThemes() + uiWatcher = new UIWatcher() + pack = atom.themes.getActiveThemes()[0] + }) + + afterEach(() => atom.themes.deactivateThemes()) + + it('reloads the theme when anything within the theme changes', () => { + spyOn(pack, 'reloadStylesheets') + spyOn(atom.themes, 'reloadBaseStylesheets') + + const watcher = uiWatcher.watchedThemes.get('theme-with-multiple-imported-files') + + expect(watcher.entities.length).toBe(6) + + watcher.entities[2].emitter.emit('did-change') + expect(pack.reloadStylesheets).toHaveBeenCalled() + expect(atom.themes.reloadBaseStylesheets).not.toHaveBeenCalled() + + watcher.entities[watcher.entities.length - 1].emitter.emit('did-change') + expect(atom.themes.reloadBaseStylesheets).toHaveBeenCalled() + }) + + it('unwatches when a theme is deactivated', async () => { + jasmine.useRealClock() + + atom.config.set('core.themes', []) + await conditionPromise(() => !uiWatcher.watchedThemes['theme-with-multiple-imported-files']) + }) + + it('watches a new theme when it is deactivated', async () => { + jasmine.useRealClock() + + atom.config.set('core.themes', ['theme-with-syntax-variables', 'theme-with-package-file']) + await conditionPromise(() => uiWatcher.watchedThemes.get('theme-with-package-file')) + + pack = atom.themes.getActiveThemes()[0] + spyOn(pack, 'reloadStylesheets') + + expect(pack.name).toBe('theme-with-package-file') + + const watcher = uiWatcher.watchedThemes.get('theme-with-package-file') + watcher.entities[2].emitter.emit('did-change') + expect(pack.reloadStylesheets).toHaveBeenCalled() + }) + }) +})