pulsar/spec/package-spec.js
2021-03-06 20:00:57 -06:00

357 lines
13 KiB
JavaScript

const path = require('path');
const Package = require('../src/package');
const ThemePackage = require('../src/theme-package');
const { mockLocalStorage } = require('./spec-helper');
describe('Package', function() {
const build = (constructor, packagePath) =>
new constructor({
path: packagePath,
packageManager: atom.packages,
config: atom.config,
styleManager: atom.styles,
notificationManager: atom.notifications,
keymapManager: atom.keymaps,
commandRegistry: atom.command,
grammarRegistry: atom.grammars,
themeManager: atom.themes,
menuManager: atom.menu,
contextMenuManager: atom.contextMenu,
deserializerManager: atom.deserializers,
viewRegistry: atom.views
});
const buildPackage = packagePath => build(Package, packagePath);
const buildThemePackage = themePath => build(ThemePackage, themePath);
describe('when the package contains incompatible native modules', function() {
beforeEach(function() {
atom.packages.devMode = false;
mockLocalStorage();
});
afterEach(() => (atom.packages.devMode = true));
it('does not activate it', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-incompatible-native-module');
const pack = buildPackage(packagePath);
expect(pack.isCompatible()).toBe(false);
expect(pack.incompatibleModules[0].name).toBe('native-module');
expect(pack.incompatibleModules[0].path).toBe(
path.join(packagePath, 'node_modules', 'native-module')
);
});
it('detects the package as incompatible even if .node file is loaded conditionally', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve(
'packages/package-with-incompatible-native-module-loaded-conditionally'
);
const pack = buildPackage(packagePath);
expect(pack.isCompatible()).toBe(false);
expect(pack.incompatibleModules[0].name).toBe('native-module');
expect(pack.incompatibleModules[0].path).toBe(
path.join(packagePath, 'node_modules', 'native-module')
);
});
it("utilizes _atomModuleCache if present to determine the package's native dependencies", function() {
let packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-ignored-incompatible-native-module');
let pack = buildPackage(packagePath);
expect(pack.getNativeModuleDependencyPaths().length).toBe(1); // doesn't see the incompatible module
expect(pack.isCompatible()).toBe(true);
packagePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/package-with-cached-incompatible-native-module')
);
pack = buildPackage(packagePath);
expect(pack.isCompatible()).toBe(false);
});
it('caches the incompatible native modules in local storage', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-incompatible-native-module');
expect(buildPackage(packagePath).isCompatible()).toBe(false);
expect(global.localStorage.getItem.callCount).toBe(1);
expect(global.localStorage.setItem.callCount).toBe(1);
expect(buildPackage(packagePath).isCompatible()).toBe(false);
expect(global.localStorage.getItem.callCount).toBe(2);
expect(global.localStorage.setItem.callCount).toBe(1);
});
it('logs an error to the console describing the problem', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-incompatible-native-module');
spyOn(console, 'warn');
spyOn(atom.notifications, 'addFatalError');
buildPackage(packagePath).activateNow();
expect(atom.notifications.addFatalError).not.toHaveBeenCalled();
expect(console.warn.callCount).toBe(1);
expect(console.warn.mostRecentCall.args[0]).toContain(
'it requires one or more incompatible native modules (native-module)'
);
});
});
describe('::rebuild()', function() {
beforeEach(function() {
atom.packages.devMode = false;
mockLocalStorage();
});
afterEach(() => (atom.packages.devMode = true));
it('returns a promise resolving to the results of `apm rebuild`', function() {
const packagePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/package-with-index')
);
const pack = buildPackage(packagePath);
const rebuildCallbacks = [];
spyOn(pack, 'runRebuildProcess').andCallFake(callback =>
rebuildCallbacks.push(callback)
);
const promise = pack.rebuild();
rebuildCallbacks[0]({
code: 0,
stdout: 'stdout output',
stderr: 'stderr output'
});
waitsFor(done =>
promise.then(function(result) {
expect(result).toEqual({
code: 0,
stdout: 'stdout output',
stderr: 'stderr output'
});
done();
})
);
});
it('persists build failures in local storage', function() {
const packagePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/package-with-index')
);
const pack = buildPackage(packagePath);
expect(pack.isCompatible()).toBe(true);
expect(pack.getBuildFailureOutput()).toBeNull();
const rebuildCallbacks = [];
spyOn(pack, 'runRebuildProcess').andCallFake(callback =>
rebuildCallbacks.push(callback)
);
pack.rebuild();
rebuildCallbacks[0]({ code: 13, stderr: 'It is broken' });
expect(pack.getBuildFailureOutput()).toBe('It is broken');
expect(pack.getIncompatibleNativeModules()).toEqual([]);
expect(pack.isCompatible()).toBe(false);
// A different package instance has the same failure output (simulates reload)
const pack2 = buildPackage(packagePath);
expect(pack2.getBuildFailureOutput()).toBe('It is broken');
expect(pack2.isCompatible()).toBe(false);
// Clears the build failure after a successful build
pack.rebuild();
rebuildCallbacks[1]({ code: 0, stdout: 'It worked' });
expect(pack.getBuildFailureOutput()).toBeNull();
expect(pack2.getBuildFailureOutput()).toBeNull();
});
it('sets cached incompatible modules to an empty array when the rebuild completes (there may be a build error, but rebuilding *deletes* native modules)', function() {
const packagePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/package-with-incompatible-native-module')
);
const pack = buildPackage(packagePath);
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0);
const rebuildCallbacks = [];
spyOn(pack, 'runRebuildProcess').andCallFake(callback =>
rebuildCallbacks.push(callback)
);
pack.rebuild();
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0);
rebuildCallbacks[0]({ code: 0, stdout: 'It worked' });
expect(pack.getIncompatibleNativeModules().length).toBe(0);
});
});
describe('theme', function() {
let [editorElement, theme] = [];
beforeEach(function() {
editorElement = document.createElement('atom-text-editor');
jasmine.attachToDOM(editorElement);
});
afterEach(() =>
waitsForPromise(function() {
if (theme != null) {
return Promise.resolve(theme.deactivate());
}
})
);
describe('when the theme contains a single style file', function() {
it('loads and applies css', function() {
expect(getComputedStyle(editorElement).paddingBottom).not.toBe(
'1234px'
);
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-with-index-css')
);
theme = buildThemePackage(themePath);
theme.activate();
expect(getComputedStyle(editorElement).paddingTop).toBe('1234px');
});
it('parses, loads and applies less', function() {
expect(getComputedStyle(editorElement).paddingBottom).not.toBe(
'1234px'
);
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-with-index-less')
);
theme = buildThemePackage(themePath);
theme.activate();
expect(getComputedStyle(editorElement).paddingTop).toBe('4321px');
});
});
describe('when the theme contains a package.json file', () =>
it('loads and applies stylesheets from package.json in the correct order', function() {
expect(getComputedStyle(editorElement).paddingTop).not.toBe('101px');
expect(getComputedStyle(editorElement).paddingRight).not.toBe('102px');
expect(getComputedStyle(editorElement).paddingBottom).not.toBe('103px');
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-with-package-file')
);
theme = buildThemePackage(themePath);
theme.activate();
expect(getComputedStyle(editorElement).paddingTop).toBe('101px');
expect(getComputedStyle(editorElement).paddingRight).toBe('102px');
expect(getComputedStyle(editorElement).paddingBottom).toBe('103px');
}));
describe('when the theme does not contain a package.json file and is a directory', () =>
it('loads all stylesheet files in the directory', function() {
expect(getComputedStyle(editorElement).paddingTop).not.toBe('10px');
expect(getComputedStyle(editorElement).paddingRight).not.toBe('20px');
expect(getComputedStyle(editorElement).paddingBottom).not.toBe('30px');
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-without-package-file')
);
theme = buildThemePackage(themePath);
theme.activate();
expect(getComputedStyle(editorElement).paddingTop).toBe('10px');
expect(getComputedStyle(editorElement).paddingRight).toBe('20px');
expect(getComputedStyle(editorElement).paddingBottom).toBe('30px');
}));
describe('reloading a theme', function() {
beforeEach(function() {
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-with-package-file')
);
theme = buildThemePackage(themePath);
theme.activate();
});
it('reloads without readding to the stylesheets list', function() {
expect(theme.getStylesheetPaths().length).toBe(3);
theme.reloadStylesheets();
expect(theme.getStylesheetPaths().length).toBe(3);
});
});
describe('events', function() {
beforeEach(function() {
const themePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/theme-with-package-file')
);
theme = buildThemePackage(themePath);
theme.activate();
});
it('deactivated event fires on .deactivate()', function() {
let spy;
theme.onDidDeactivate((spy = jasmine.createSpy()));
waitsForPromise(() => Promise.resolve(theme.deactivate()));
runs(() => expect(spy).toHaveBeenCalled());
});
});
});
describe('.loadMetadata()', function() {
let [packagePath, metadata] = [];
beforeEach(function() {
packagePath = __guard__(atom.project.getDirectories()[0], x =>
x.resolve('packages/package-with-different-directory-name')
);
metadata = atom.packages.loadPackageMetadata(packagePath, true);
});
it('uses the package name defined in package.json', () =>
expect(metadata.name).toBe('package-with-a-totally-different-name'));
});
describe('the initialize() hook', function() {
it('gets called when the package is activated', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-deserializers');
const pack = buildPackage(packagePath);
pack.requireMainModule();
const { mainModule } = pack;
spyOn(mainModule, 'initialize');
expect(mainModule.initialize).not.toHaveBeenCalled();
pack.activate();
expect(mainModule.initialize).toHaveBeenCalled();
expect(mainModule.initialize.callCount).toBe(1);
});
it('gets called when a deserializer is used', function() {
const packagePath = atom.project
.getDirectories()[0]
.resolve('packages/package-with-deserializers');
const pack = buildPackage(packagePath);
pack.requireMainModule();
const { mainModule } = pack;
spyOn(mainModule, 'initialize');
pack.load();
expect(mainModule.initialize).not.toHaveBeenCalled();
atom.deserializers.deserialize({ deserializer: 'Deserializer1', a: 'b' });
expect(mainModule.initialize).toHaveBeenCalled();
});
});
});
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null
? transform(value)
: undefined;
}