Skip to content

Commit

Permalink
Change behavior of MagicPen#clone so it's more compatible with expect…
Browse files Browse the repository at this point in the history
….child()

* Fix inheritance of the styles property in clones
* Fix inheritance of the _themes property in clones
* Allow installing plugins in clones that are already installed in the parent,
  while allowing a plugin requirement to be fulfilled by a plugin installed
  in the parent
* Instate a more complex detection of whether a built-in style is being redefined
* Prohibit definition of styles for which the corresponding property has been set
  to false (maintaining Unexpected's ability to prohibit the definition of styles
  named "inline" and "diff" in clones).
  • Loading branch information
papandreou committed Mar 8, 2017
1 parent d9afdca commit 080b19f
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 26 deletions.
74 changes: 51 additions & 23 deletions lib/MagicPen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ var duplicateText = require('./duplicateText');
var rgbRegexp = require('./rgbRegexp');
var cssStyles = require('./cssStyles');

var builtInStyleNames = [
'bold', 'dim', 'italic', 'underline', 'inverse', 'hidden',
'strikeThrough', 'black', 'red', 'green', 'yellow', 'blue',
'magenta', 'cyan', 'white', 'gray', 'bgBlack', 'bgRed',
'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta', 'bgCyan',
'bgWhite'
];

function MagicPen(options) {
if (!(this instanceof MagicPen)) {
return new MagicPen(options);
Expand All @@ -24,7 +32,12 @@ function MagicPen(options) {
this.output = [[]];
this.styles = Object.create(null);
this.installedPlugins = [];
this._themes = {};
// Ready to be cloned individually:
this._themes = {
html: { styles: {} },
ansi: { styles: {} },
text: { styles: {} }
};
this.preferredWidth = (!process.browser && process.stdout.columns) || 80;
if (options.format) {
this.format = options.format;
Expand Down Expand Up @@ -145,19 +158,22 @@ MagicPen.prototype.outdentLines = function () {
};

MagicPen.prototype.addStyle = function (style, handler, allowRedefinition) {
var existingType = typeof this[style];
if (existingType === 'function') {
if (!allowRedefinition) {
throw new Error('"' + style + '" style is already defined, set 3rd arg (allowRedefinition) to true to define it anyway');
}
} else if (existingType !== 'undefined') {
if (this[style] === false || ((this.hasOwnProperty(style) || MagicPen.prototype[style]) && !Object.prototype.hasOwnProperty.call(this.styles, style) && builtInStyleNames.indexOf(style) === -1)) {
throw new Error('"' + style + '" style cannot be defined, it clashes with a built-in attribute');
}

var styles = this.styles;
this.styles = Object.create(null);
for (var p in styles) {
this.styles[p] = styles[p];
// Refuse to redefine a built-in style or a style already defined directly on this pen unless allowRedefinition is true:
if (this.hasOwnProperty(style) || builtInStyleNames.indexOf(style) !== -1) {
var existingType = typeof this[style];
if (existingType === 'function') {
if (!allowRedefinition) {
throw new Error('"' + style + '" style is already defined, set 3rd arg (allowRedefinition) to true to define it anyway');
}
}
}
if (this._stylesHaveNotBeenClonedYet) {
this.styles = Object.create(this.styles);
this._stylesHaveNotBeenClonedYet = false;
}

this.styles[style] = handler;
Expand Down Expand Up @@ -437,13 +453,7 @@ MagicPen.prototype.space = MagicPen.prototype.sp = function (count) {
return this.text(duplicateText(' ', count));
};

[
'bold', 'dim', 'italic', 'underline', 'inverse', 'hidden',
'strikeThrough', 'black', 'red', 'green', 'yellow', 'blue',
'magenta', 'cyan', 'white', 'gray', 'bgBlack', 'bgRed',
'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta', 'bgCyan',
'bgWhite'
].forEach(function (textStyle) {
builtInStyleNames.forEach(function (textStyle) {
MagicPen.prototype[textStyle] = MagicPen.prototype[textStyle.toLowerCase()] = function (content) {
return this.text(content, textStyle);
};
Expand All @@ -458,11 +468,14 @@ MagicPen.prototype.clone = function (format) {
MagicPenClone.prototype = this;
var clonedPen = new MagicPenClone();
clonedPen.styles = this.styles;
clonedPen._stylesHaveNotBeenClonedYet = true;
clonedPen.indentationLevel = 0;
clonedPen.output = [[]];
clonedPen.installedPlugins = this.installedPlugins;
clonedPen.installedPlugins = [];
clonedPen._themes = this._themes;
clonedPen._themesHaveNotBeenClonedYet = true;
clonedPen.format = format || this.format;
clonedPen.parent = this;
return clonedPen;
};

Expand Down Expand Up @@ -513,10 +526,17 @@ MagicPen.prototype.use = function (plugin) {
}

if (plugin.dependencies) {
var installedPlugins = this.installedPlugins;
var instance = this;
var thisAndParents = [];
do {
thisAndParents.push(instance);
instance = instance.parent;
} while (instance);
var unfulfilledDependencies = plugin.dependencies.filter(function (dependency) {
return !installedPlugins.some(function (plugin) {
return plugin.name === dependency;
return !thisAndParents.some(function (instance) {
return instance.installedPlugins.some(function (plugin) {
return plugin.name === dependency;
});
});
});

Expand Down Expand Up @@ -667,6 +687,15 @@ MagicPen.prototype.installTheme = function (formats, theme) {
};
}

if (that._themesHaveNotBeenClonedYet) {
var clonedThemes = {};
Object.keys(that._themes).forEach(function (format) {
clonedThemes[format] = Object.create(that._themes[format]);
});
that._themes = clonedThemes;
that._themesHaveNotBeenClonedYet = false;
}

Object.keys(theme.styles).forEach(function (themeKey) {
if (rgbRegexp.test(themeKey) || cssStyles[themeKey]) {
throw new Error("Invalid theme key: '" + themeKey + "' you can't map build styles.");
Expand All @@ -679,7 +708,6 @@ MagicPen.prototype.installTheme = function (formats, theme) {
}
});

that._themes = extend({}, that._themes);
formats.forEach(function (format) {
var baseTheme = that._themes[format] || { styles: {} };
var extendedTheme = extend({}, baseTheme, theme);
Expand Down
70 changes: 67 additions & 3 deletions test/magicpen.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ describe('magicpen', function () {
return pen;
}

it('throws when creating a custom style with a name that already exists', function () {
forEach(['red', 'write', 'addStyle'], function (name) {
it('throws when creating a custom style with a name that already exists as a built-in style', function () {
expect(function () {
magicpen().addStyle('red', function () {});
}, 'to throw', '"red" style is already defined, set 3rd arg (allowRedefinition) to true to define it anyway');
});

it('throws when creating a custom style that clashes with a built-in one', function () {
forEach(['write', 'addStyle'], function (name) {
expect(function () {
magicpen().addStyle(name, function () {});
}, 'to throw', '"' + name + '" style is already defined, set 3rd arg (allowRedefinition) to true to define it anyway');
}, 'to throw', '"' + name + '" style cannot be defined, it clashes with a built-in attribute');
});
});

Expand Down Expand Up @@ -1751,4 +1757,62 @@ describe('magicpen', function () {
expect(magicpen().text('foo').isAtStartOfLine(), 'to be false');
});
});

// Unexpected uses this trick to prevent 'diff' and 'inline' styles from being added:
describe('when setting a property to false', function () {
it('prohibits definition of a style of that name', function () {
pen.foo = false;
expect(function () {
pen.addStyle('foo', function () {});
}, 'to throw', '"foo" style cannot be defined, it clashes with a built-in attribute');
});

it('prohibits definition of a style of that name on a clone', function () {
pen.foo = false;
expect(function () {
pen.clone().addStyle('foo', function () {});
}, 'to throw', '"foo" style cannot be defined, it clashes with a built-in attribute');
});
});

describe('when redefining styles in a clone', function () {
var clone;
beforeEach(function () {
pen.addStyle('someStyle', function () {
this.text('parent');
});
clone = pen.clone();
});

it('should allow redefining a style in a clone without requiring the allowRedefinition to be true', function () {
clone.addStyle('someStyle', function () {
this.text('clone');
});
expect(clone.someStyle().toString(), 'to equal', 'clone');
});

it('should not affect the parent', function () {
clone.addStyle('someStyle', function () {
this.text('clone');
});
expect(clone.someStyle().toString(), 'to equal', 'clone');
expect(pen.someStyle().toString(), 'to equal', 'parent');
});

it('should lazy-clone the styles object', function () {
expect(clone.styles, 'to be', pen.styles);
clone.addStyle('someStyle', function () {
this.text('clone');
});
expect(clone.styles, 'not to be', pen.styles);
});

it('should lazy-clone the _themes object', function () {
expect(clone._themes, 'to be', pen._themes);
expect(clone._themes.html, 'to be', pen._themes.html);
clone.installTheme({});
expect(clone._themes, 'not to be', pen._themes);
expect(clone._themes.html, 'not to be', pen._themes.html);
});
});
});

0 comments on commit 080b19f

Please sign in to comment.