diff --git a/package-lock.json b/package-lock.json
index 90bfb78b1..e212703a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2771,8 +2771,7 @@
}
},
"incompatible-packages": {
- "version": "https://www.atom.io/api/packages/incompatible-packages/versions/0.27.3/tarball",
- "integrity": "sha512-OlkFBSpvHH7dUfYQTlcgTXEa+sjr9Es8d2lNPGPS2O5Rp5MiRKcnovQoMtaF3fkcuV2O7onim45ldFjbl4qdog==",
+ "version": "file:packages/incompatible-packages",
"requires": {
"etch": "^0.12.2"
}
diff --git a/package.json b/package.json
index 354a49f22..a6e89b59d 100644
--- a/package.json
+++ b/package.json
@@ -73,7 +73,7 @@
"grammar-selector": "https://www.atom.io/api/packages/grammar-selector/versions/0.50.1/tarball",
"grim": "1.5.0",
"image-view": "https://www.atom.io/api/packages/image-view/versions/0.63.1/tarball",
- "incompatible-packages": "https://www.atom.io/api/packages/incompatible-packages/versions/0.27.3/tarball",
+ "incompatible-packages": "file:packages/incompatible-packages",
"jasmine-json": "~0.0",
"jasmine-reporters": "1.1.0",
"jasmine-tagged": "^1.1.4",
@@ -207,7 +207,7 @@
"go-to-line": "0.33.0",
"grammar-selector": "0.50.1",
"image-view": "0.63.1",
- "incompatible-packages": "0.27.3",
+ "incompatible-packages": "file:./packages/incompatible-packages",
"keybinding-resolver": "0.38.4",
"line-ending-selector": "0.7.7",
"link": "0.31.6",
diff --git a/packages/README.md b/packages/README.md
index 0917a2541..b88d01d34 100644
--- a/packages/README.md
+++ b/packages/README.md
@@ -38,7 +38,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **go-to-line** | [`atom/go-to-line`][go-to-line] | [#17844](https://github.com/atom/atom/issues/17844) |
| **grammar-selector** | [`atom/grammar-selector`][grammar-selector] | [#17845](https://github.com/atom/atom/issues/17845) |
| **image-view** | [`atom/image-view`][image-view] | |
-| **incompatible-packages** | [`atom/incompatible-packages`][incompatible-packages] | [#17846](https://github.com/atom/atom/issues/17846) |
+| **incompatible-packages** | [`./packages/incompatible-packages`][./incompatible-packages] | [#17846](https://github.com/atom/atom/issues/17846) |
| **keybinding-resolver** | [`atom/keybinding-resolver`][keybinding-resolver] | |
| **language-c** | [`atom/language-c`][language-c] | |
| **language-clojure** | [`atom/language-clojure`][language-clojure] | |
diff --git a/packages/incompatible-packages/.gitignore b/packages/incompatible-packages/.gitignore
new file mode 100644
index 000000000..ade14b919
--- /dev/null
+++ b/packages/incompatible-packages/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+npm-debug.log
+node_modules
diff --git a/packages/incompatible-packages/LICENSE.md b/packages/incompatible-packages/LICENSE.md
new file mode 100644
index 000000000..4d231b456
--- /dev/null
+++ b/packages/incompatible-packages/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/incompatible-packages/README.md b/packages/incompatible-packages/README.md
new file mode 100644
index 000000000..43b92d7c4
--- /dev/null
+++ b/packages/incompatible-packages/README.md
@@ -0,0 +1,6 @@
+# Incompatible Packages package
+
+Displays a list of installed Atom packages that have native module
+dependencies that are not compatible with the current version of Atom.
+
+
diff --git a/packages/incompatible-packages/lib/incompatible-packages-component.js b/packages/incompatible-packages/lib/incompatible-packages-component.js
new file mode 100644
index 000000000..8eb6c62c6
--- /dev/null
+++ b/packages/incompatible-packages/lib/incompatible-packages-component.js
@@ -0,0 +1,228 @@
+/** @babel */
+/** @jsx etch.dom */
+
+import {BufferedProcess} from 'atom'
+import etch from 'etch'
+
+import VIEW_URI from './view-uri'
+const REBUILDING = 'rebuilding'
+const REBUILD_FAILED = 'rebuild-failed'
+const REBUILD_SUCCEEDED = 'rebuild-succeeded'
+
+export default class IncompatiblePackagesComponent {
+ constructor (packageManager) {
+ this.rebuildStatuses = new Map
+ this.rebuildFailureOutputs = new Map
+ this.rebuildInProgress = false
+ this.rebuiltPackageCount = 0
+ this.packageManager = packageManager
+ this.loaded = false
+ etch.initialize(this)
+
+ if (this.packageManager.getActivePackages().length > 0) {
+ this.populateIncompatiblePackages()
+ } else {
+ global.setImmediate(this.populateIncompatiblePackages.bind(this))
+ }
+
+ this.element.addEventListener('click', (event) => {
+ if (event.target === this.refs.rebuildButton) {
+ this.rebuildIncompatiblePackages()
+ } else if (event.target === this.refs.reloadButton) {
+ atom.reload()
+ } else if (event.target.classList.contains('view-settings')) {
+ atom.workspace.open(`atom://config/packages/${event.target.package.name}`)
+ }
+ })
+ }
+
+ update () {}
+
+ render () {
+ if (!this.loaded) {
+ return
Loading...
+ }
+
+ return (
+
+ {this.renderHeading()}
+ {this.renderIncompatiblePackageList()}
+
+ )
+ }
+
+ renderHeading () {
+ if (this.incompatiblePackages.length > 0) {
+ if (this.rebuiltPackageCount > 0) {
+ let alertClass =
+ (this.rebuiltPackageCount === this.incompatiblePackages.length)
+ ? 'alert-success icon-check'
+ : 'alert-warning icon-bug'
+
+ return (
+
+ {this.rebuiltPackageCount} of {this.incompatiblePackages.length} packages
+ were rebuilt successfully. Reload Atom to activate them.
+
+
+
+ )
+ } else {
+ return (
+
+ Some installed packages could not be loaded because they contain native
+ modules that were compiled for an earlier version of Atom.
+
+
+
+ )
+ }
+ } else {
+ return (
+
+ None of your packages contain incompatible native modules.
+
+ )
+ }
+ }
+
+ renderIncompatiblePackageList () {
+ return (
+ {
+ this.incompatiblePackages.map(this.renderIncompatiblePackage.bind(this))
+ }
+ )
+ }
+
+ renderIncompatiblePackage (pack) {
+ let rebuildStatus = this.rebuildStatuses.get(pack)
+
+ return (
+
+ {this.renderRebuildStatusIndicator(rebuildStatus)}
+
+
+ {pack.name} {pack.metadata.version}
+
+ {
+ rebuildStatus
+ ? this.renderRebuildOutput(pack)
+ : this.renderIncompatibleModules(pack)
+ }
+
+ )
+ }
+
+ renderRebuildStatusIndicator (rebuildStatus) {
+ if (rebuildStatus === REBUILDING) {
+ return (
+
+ Rebuilding
+
+ )
+ } else if (rebuildStatus === REBUILD_SUCCEEDED) {
+ return (
+
+ Rebuild Succeeded
+
+ )
+ } else if (rebuildStatus === REBUILD_FAILED) {
+ return (
+
+ Rebuild Failed
+
+ )
+ } else {
+ return ''
+ }
+ }
+
+ renderRebuildOutput (pack) {
+ if (this.rebuildStatuses.get(pack) === REBUILD_FAILED) {
+ return {this.rebuildFailureOutputs.get(pack)}
+ } else {
+ return ''
+ }
+ }
+
+ renderIncompatibleModules (pack) {
+ return (
+
+ )
+ }
+
+ populateIncompatiblePackages () {
+ this.incompatiblePackages =
+ this.packageManager
+ .getLoadedPackages()
+ .filter(pack => !pack.isCompatible())
+
+ for (let pack of this.incompatiblePackages) {
+ let buildFailureOutput = pack.getBuildFailureOutput()
+ if (buildFailureOutput) {
+ this.setPackageStatus(pack, REBUILD_FAILED)
+ this.setRebuildFailureOutput(pack, buildFailureOutput)
+ }
+ }
+
+ this.loaded = true
+ etch.update(this)
+ }
+
+ async rebuildIncompatiblePackages () {
+ this.rebuildInProgress = true
+ let rebuiltPackageCount = 0
+ for (let pack of this.incompatiblePackages) {
+ this.setPackageStatus(pack, REBUILDING)
+ let {code, stderr} = await pack.rebuild()
+ if (code === 0) {
+ this.setPackageStatus(pack, REBUILD_SUCCEEDED)
+ rebuiltPackageCount++
+ } else {
+ this.setRebuildFailureOutput(pack, stderr)
+ this.setPackageStatus(pack, REBUILD_FAILED)
+ }
+ }
+ this.rebuildInProgress = false
+ this.rebuiltPackageCount = rebuiltPackageCount
+ etch.update(this)
+ }
+
+ setPackageStatus (pack, status) {
+ this.rebuildStatuses.set(pack, status)
+ etch.update(this)
+ }
+
+ setRebuildFailureOutput (pack, output) {
+ this.rebuildFailureOutputs.set(pack, output)
+ etch.update(this)
+ }
+
+ getTitle () {
+ return 'Incompatible Packages'
+ }
+
+ getURI () {
+ return VIEW_URI
+ }
+
+ getIconName () {
+ return 'package'
+ }
+
+ serialize () {
+ return {deserializer: 'IncompatiblePackagesComponent'}
+ }
+}
diff --git a/packages/incompatible-packages/lib/main.js b/packages/incompatible-packages/lib/main.js
new file mode 100644
index 000000000..b936cb880
--- /dev/null
+++ b/packages/incompatible-packages/lib/main.js
@@ -0,0 +1,52 @@
+/** @babel */
+
+import {Disposable, CompositeDisposable} from 'atom'
+import VIEW_URI from './view-uri'
+
+let disposables = null
+
+export function activate () {
+ disposables = new CompositeDisposable()
+
+ disposables.add(atom.workspace.addOpener((uri) => {
+ if (uri === VIEW_URI) {
+ return deserializeIncompatiblePackagesComponent()
+ }
+ }))
+
+ disposables.add(atom.commands.add('atom-workspace', {
+ 'incompatible-packages:view': () => {
+ atom.workspace.open(VIEW_URI)
+ }
+ }))
+}
+
+export function deactivate () {
+ disposables.dispose()
+}
+
+export function consumeStatusBar (statusBar) {
+ let incompatibleCount = 0
+ for (let pack of atom.packages.getLoadedPackages()) {
+ if (!pack.isCompatible()) incompatibleCount++
+ }
+
+ if (incompatibleCount > 0) {
+ let icon = createIcon(incompatibleCount)
+ let tile = statusBar.addRightTile({item: icon, priority: 200})
+ icon.element.addEventListener('click', () => {
+ atom.commands.dispatch(icon.element, 'incompatible-packages:view')
+ })
+ disposables.add(new Disposable(() => tile.destroy()))
+ }
+}
+
+export function deserializeIncompatiblePackagesComponent () {
+ const IncompatiblePackagesComponent = require('./incompatible-packages-component')
+ return new IncompatiblePackagesComponent(atom.packages)
+}
+
+function createIcon (count) {
+ const StatusIconComponent = require('./status-icon-component')
+ return new StatusIconComponent({count})
+}
diff --git a/packages/incompatible-packages/lib/status-icon-component.js b/packages/incompatible-packages/lib/status-icon-component.js
new file mode 100644
index 000000000..72653ca9b
--- /dev/null
+++ b/packages/incompatible-packages/lib/status-icon-component.js
@@ -0,0 +1,22 @@
+/** @babel */
+/** @jsx etch.dom */
+
+import etch from 'etch'
+
+export default class StatusIconComponent {
+ constructor ({count}) {
+ this.count = count
+ etch.initialize(this)
+ }
+
+ update () {}
+
+ render () {
+ return (
+
+
+ {this.count}
+
+ )
+ }
+}
diff --git a/packages/incompatible-packages/lib/view-uri.js b/packages/incompatible-packages/lib/view-uri.js
new file mode 100644
index 000000000..de66f3ac6
--- /dev/null
+++ b/packages/incompatible-packages/lib/view-uri.js
@@ -0,0 +1,3 @@
+/** @babel */
+
+export default 'atom://incompatible-packages'
diff --git a/packages/incompatible-packages/package.json b/packages/incompatible-packages/package.json
new file mode 100644
index 000000000..170a69c8c
--- /dev/null
+++ b/packages/incompatible-packages/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "incompatible-packages",
+ "main": "./lib/main",
+ "version": "0.27.3",
+ "description": "Show incompatible packages",
+ "repository": "https://github.com/atom/atom",
+ "license": "MIT",
+ "engines": {
+ "atom": ">0.50.0"
+ },
+ "dependencies": {
+ "etch": "^0.12.2"
+ },
+ "consumedServices": {
+ "status-bar": {
+ "versions": {
+ "^1.0.0": "consumeStatusBar"
+ }
+ }
+ },
+ "deserializers": {
+ "IncompatiblePackagesComponent": "deserializeIncompatiblePackagesComponent"
+ }
+}
diff --git a/packages/incompatible-packages/spec/fixtures/incompatible-package/bad.js b/packages/incompatible-packages/spec/fixtures/incompatible-package/bad.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/incompatible-packages/spec/fixtures/incompatible-package/package.json b/packages/incompatible-packages/spec/fixtures/incompatible-package/package.json
new file mode 100644
index 000000000..bb8aeca55
--- /dev/null
+++ b/packages/incompatible-packages/spec/fixtures/incompatible-package/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "incompatible-package",
+ "version": "1.0.0",
+ "main": "./bad.js"
+}
diff --git a/packages/incompatible-packages/spec/incompatible-packages-component-spec.js b/packages/incompatible-packages/spec/incompatible-packages-component-spec.js
new file mode 100644
index 000000000..88d46677e
--- /dev/null
+++ b/packages/incompatible-packages/spec/incompatible-packages-component-spec.js
@@ -0,0 +1,243 @@
+/** @babel */
+
+import etch from 'etch'
+import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'
+
+describe('IncompatiblePackagesComponent', () => {
+ let packages, etchScheduler
+
+ beforeEach(() => {
+ etchScheduler = etch.getScheduler()
+
+ packages = [
+ {
+ name: 'incompatible-1',
+ isCompatible () {
+ return false
+ },
+ rebuild: function () {
+ return new Promise((resolve) => this.resolveRebuild = resolve)
+ },
+ getBuildFailureOutput () {
+ return null
+ },
+ path: '/Users/joe/.atom/packages/incompatible-1',
+ metadata: {
+ repository: 'https://github.com/atom/incompatible-1',
+ version: '1.0.0'
+ },
+ incompatibleModules: [
+ {name: 'x', version: '1.0.0', error: 'Expected version X, got Y'},
+ {name: 'y', version: '1.0.0', error: 'Expected version X, got Z'}
+ ]
+ },
+ {
+ name: 'incompatible-2',
+ isCompatible () {
+ return false
+ },
+ rebuild () {
+ return new Promise((resolve) => this.resolveRebuild = resolve)
+ },
+ getBuildFailureOutput () {
+ return null
+ },
+ path: '/Users/joe/.atom/packages/incompatible-2',
+ metadata: {
+ repository: 'https://github.com/atom/incompatible-2',
+ version: '1.0.0'
+ },
+ incompatibleModules: [
+ {name: 'z', version: '1.0.0', error: 'Expected version X, got Y'}
+ ]
+ },
+ {
+ name: 'compatible',
+ isCompatible () {
+ return true
+ },
+ rebuild () {
+ throw new Error('Should not rebuild a compatible package')
+ },
+ getBuildFailureOutput () {
+ return null
+ },
+ path: '/Users/joe/.atom/packages/b',
+ metadata: {
+ repository: 'https://github.com/atom/b',
+ version: '1.0.0'
+ },
+ incompatibleModules: [],
+ }
+ ]
+ })
+
+ describe('when packages have not finished loading', () => {
+ it('delays rendering incompatible packages until the end of the tick', () => {
+ waitsForPromise(async () => {
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => [],
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+
+ expect(element.querySelectorAll('.incompatible-package').length).toEqual(0)
+
+ await etchScheduler.getNextUpdatePromise()
+
+ expect(element.querySelectorAll('.incompatible-package').length).toBeGreaterThan(0)
+ })
+ })
+ })
+
+ describe('when there are no incompatible packages', () => {
+ it('does not render incompatible packages or the rebuild button', () => {
+ waitsForPromise(async () => {
+ expect(packages[2].isCompatible()).toBe(true)
+ let compatiblePackages = [packages[2]]
+
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => compatiblePackages,
+ getLoadedPackages: () => compatiblePackages
+ })
+ let {element} = component
+
+ await etchScheduler.getNextUpdatePromise()
+
+ expect(element.querySelectorAll('.incompatible-package').length).toBe(0)
+ expect(element.querySelector('button')).toBeNull()
+ })
+ })
+ })
+
+ describe('when some packages previously failed to rebuild', () => {
+ it('renders them with failed build status and error output', () => {
+ waitsForPromise(async () => {
+ packages[1].getBuildFailureOutput = function () {
+ return 'The build failed'
+ }
+
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => packages,
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+
+ await etchScheduler.getNextUpdatePromise()
+ let packageElement = element.querySelector('.incompatible-package:nth-child(2)')
+
+ expect(packageElement.querySelector('.badge').textContent).toBe('Rebuild Failed')
+ expect(packageElement.querySelector('pre').textContent).toBe('The build failed')
+ })
+ })
+ })
+
+ describe('when there are incompatible packages', () => {
+ it('renders incompatible packages and the rebuild button', () => {
+ waitsForPromise(async () => {
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => packages,
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+
+ await etchScheduler.getNextUpdatePromise()
+
+ expect(element.querySelectorAll('.incompatible-package').length).toEqual(2)
+ expect(element.querySelector('button')).not.toBeNull()
+ })
+ })
+
+ describe('when the "Rebuild All" button is clicked', () => {
+ it('rebuilds every incompatible package, updating each package\'s view with status', () => {
+ waitsForPromise(async () => {
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => packages,
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+ jasmine.attachToDOM(element)
+
+ await etchScheduler.getNextUpdatePromise()
+
+ component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
+ await etchScheduler.getNextUpdatePromise() // view update
+
+ expect(component.refs.rebuildButton.disabled).toBe(true)
+
+ expect(packages[0].resolveRebuild).toBeDefined()
+
+ expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuilding')
+ expect(element.querySelector('.incompatible-package:nth-child(2) .badge')).toBeNull()
+
+ packages[0].resolveRebuild({code: 0}) // simulate rebuild success
+ await etchScheduler.getNextUpdatePromise() // view update
+
+ expect(packages[1].resolveRebuild).toBeDefined()
+
+ expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
+ expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuilding')
+
+ packages[1].resolveRebuild({code: 12, stderr: 'This is an error from the test!'}) // simulate rebuild failure
+ await etchScheduler.getNextUpdatePromise() // view update
+
+ expect(element.querySelector('.incompatible-package:nth-child(1) .badge').textContent).toBe('Rebuild Succeeded')
+ expect(element.querySelector('.incompatible-package:nth-child(2) .badge').textContent).toBe('Rebuild Failed')
+ expect(element.querySelector('.incompatible-package:nth-child(2) pre').textContent).toBe('This is an error from the test!')
+ })
+ })
+
+ it('displays a prompt to reload Atom when the packages finish rebuilding', () => {
+ waitsForPromise(async () => {
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => packages,
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+ jasmine.attachToDOM(element)
+ await etchScheduler.getNextUpdatePromise() // view update
+
+ component.refs.rebuildButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
+ expect(packages[0].resolveRebuild({code: 0}))
+ await new Promise(global.setImmediate)
+ expect(packages[1].resolveRebuild({code: 0}))
+
+ await etchScheduler.getNextUpdatePromise() // view update
+
+ expect(component.refs.reloadButton).toBeDefined()
+ expect(element.querySelector('.alert').textContent).toMatch(/2 of 2/)
+
+ spyOn(atom, 'reload')
+ component.refs.reloadButton.dispatchEvent(new CustomEvent('click', {bubbles: true}))
+ expect(atom.reload).toHaveBeenCalled()
+ })
+ })
+ })
+
+ describe('when the "Package Settings" button is clicked', () => {
+ it('opens the settings panel for the package', () => {
+ waitsForPromise(async () => {
+ let component =
+ new IncompatiblePackagesComponent({
+ getActivePackages: () => packages,
+ getLoadedPackages: () => packages
+ })
+ let {element} = component
+ jasmine.attachToDOM(element)
+
+ await etchScheduler.getNextUpdatePromise()
+
+ spyOn(atom.workspace, 'open')
+ element.querySelector('.incompatible-package:nth-child(2) button').dispatchEvent(new CustomEvent('click', {bubbles: true}))
+ expect(atom.workspace.open).toHaveBeenCalledWith('atom://config/packages/incompatible-2')
+ })
+ })
+ })
+ })
+})
diff --git a/packages/incompatible-packages/spec/incompatible-packages-spec.js b/packages/incompatible-packages/spec/incompatible-packages-spec.js
new file mode 100644
index 000000000..fa6e7e76a
--- /dev/null
+++ b/packages/incompatible-packages/spec/incompatible-packages-spec.js
@@ -0,0 +1,77 @@
+/** @babel */
+
+import path from 'path'
+import IncompatiblePackagesComponent from '../lib/incompatible-packages-component'
+import StatusIconComponent from '../lib/status-icon-component'
+
+// This exists only so that CI passes on both Atom 1.6 and Atom 1.8+.
+function findStatusBar () {
+ if (typeof atom.workspace.getFooterPanels === 'function') {
+ const footerPanels = atom.workspace.getFooterPanels()
+ if (footerPanels.length > 0) {
+ return footerPanels[0].getItem()
+ }
+ }
+
+ return atom.workspace.getBottomPanels()[0].getItem()
+}
+
+describe('Incompatible packages', () => {
+ let statusBar
+
+ beforeEach(() => {
+ atom.views.getView(atom.workspace)
+
+ waitsForPromise(() => atom.packages.activatePackage('status-bar'))
+
+ runs(() => {
+ statusBar = findStatusBar()
+ })
+ })
+
+ describe('when there are packages with incompatible native modules', () => {
+ beforeEach(() => {
+ let incompatiblePackage = atom.packages.loadPackage(
+ path.join(__dirname, 'fixtures', 'incompatible-package')
+ )
+ spyOn(incompatiblePackage, 'isCompatible').andReturn(false)
+ incompatiblePackage.incompatibleModules = []
+ waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
+
+ waits(1)
+ })
+
+ it('adds an icon to the status bar', () => {
+ let statusBarIcon = statusBar.getRightTiles()[0].getItem()
+ expect(statusBarIcon.constructor).toBe(StatusIconComponent)
+ })
+
+ describe('clicking the icon', () => {
+ it('displays the incompatible packages view in a pane', () => {
+ let statusBarIcon = statusBar.getRightTiles()[0].getItem()
+ statusBarIcon.element.dispatchEvent(new MouseEvent('click'))
+
+ let activePaneItem
+ waitsFor(() => (activePaneItem = atom.workspace.getActivePaneItem()))
+
+ runs(() => {
+ expect(activePaneItem.constructor).toBe(IncompatiblePackagesComponent)
+ })
+ })
+ })
+ })
+
+ describe('when there are no packages with incompatible native modules', () => {
+ beforeEach(() => {
+ waitsForPromise(() => atom.packages.activatePackage("incompatible-packages"))
+ })
+
+ it('does not add an icon to the status bar', () => {
+ let statusBarItemClasses = statusBar
+ .getRightTiles()
+ .map((tile) => tile.getItem().className)
+
+ expect(statusBarItemClasses).not.toContain('incompatible-packages')
+ })
+ })
+})
diff --git a/packages/incompatible-packages/styles/incompatible-packages.less b/packages/incompatible-packages/styles/incompatible-packages.less
new file mode 100644
index 000000000..df6a5ff1e
--- /dev/null
+++ b/packages/incompatible-packages/styles/incompatible-packages.less
@@ -0,0 +1,42 @@
+@import "ui-variables";
+
+.incompatible-packages {
+ background-color: @pane-item-background-color;
+ overflow-y: scroll;
+
+ .incompatible-package {
+ padding: 15px;
+ margin-bottom: 10px;
+ border-radius: 6px;
+ border: 1px solid #d1d1d2;
+ background-color: #fafafa;
+ overflow: hidden;
+
+ .badge {
+ margin-left: 1em;
+ }
+
+ .heading {
+ margin-top: 0px;
+ }
+
+ ul {
+ padding-left: 1em;
+ }
+
+ li {
+ list-style-type: none;
+ }
+
+ pre {
+ margin-top: 2em;
+ max-height: 25em;
+ overflow: scroll;
+ color: @text-color-error;
+ }
+ }
+}
+
+.incompatible-packages-status {
+ padding-left: 2px;
+}