feat: add gradient and theme features with corresponding tests and documentation
This commit is contained in:
parent
aa06bb5ac3
commit
c017dd5b04
8 changed files with 505 additions and 13 deletions
39
examples/gradient.js
Normal file
39
examples/gradient.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import chalk from '../source/index.js';
|
||||||
|
|
||||||
|
// Set color level to enable gradients (adjust based on terminal support)
|
||||||
|
chalk.level = 3;
|
||||||
|
|
||||||
|
// Demonstrate gradient feature
|
||||||
|
console.log(chalk.bold('Chalk Gradient Examples'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Simple two-color gradient
|
||||||
|
console.log('Two-color gradient:');
|
||||||
|
console.log(chalk.gradient('#ff0000', '#0000ff')('Hello World'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// RGB array gradient
|
||||||
|
console.log('RGB array gradient:');
|
||||||
|
console.log(chalk.gradient([255, 0, 0], [0, 255, 0], [0, 0, 255])('Rainbow Text'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Multi-color gradient
|
||||||
|
console.log('Multi-color gradient:');
|
||||||
|
console.log(chalk.gradient('#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff')('Color Spectrum'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Combining with other styles
|
||||||
|
console.log('Gradient with bold:');
|
||||||
|
console.log(chalk.gradient('#ff0080', '#8000ff').bold('Bold Gradient'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Gradient on themed chalk
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
rainbow: chalk.gradient('#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff'),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Themed gradient:');
|
||||||
|
console.log(themedChalk.rainbow('Themed Rainbow Text'));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log(chalk.bold('Gradient Complete!'));
|
||||||
34
examples/theme.js
Normal file
34
examples/theme.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import chalk from '../source/index.js';
|
||||||
|
|
||||||
|
// Set color level to enable colors (adjust based on terminal support)
|
||||||
|
chalk.level = 3;
|
||||||
|
|
||||||
|
// Define a custom theme with named styles
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
error: chalk.red.bold,
|
||||||
|
success: chalk.green,
|
||||||
|
warning: chalk.yellow.underline,
|
||||||
|
info: chalk.blue,
|
||||||
|
title: chalk.magenta.bold.underline,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demonstrate the theme in action
|
||||||
|
console.log(themedChalk.title('Chalk Theme Example'));
|
||||||
|
console.log(); // Empty line
|
||||||
|
|
||||||
|
console.log(themedChalk.error('This is an error message'));
|
||||||
|
console.log(themedChalk.success('This is a success message'));
|
||||||
|
console.log(themedChalk.warning('This is a warning message'));
|
||||||
|
console.log(themedChalk.info('This is an info message'));
|
||||||
|
console.log(); // Empty line
|
||||||
|
|
||||||
|
// Show that original styles still work
|
||||||
|
console.log(themedChalk.red('Still works with original styles'));
|
||||||
|
console.log(themedChalk.bold('Bold text from themed chalk'));
|
||||||
|
|
||||||
|
// Demonstrate chaining with theme styles
|
||||||
|
console.log(themedChalk.error.bgWhite('Error on white background'));
|
||||||
|
console.log(themedChalk.success.underline('Underlined success'));
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log(themedChalk.title('Theme Complete!'));
|
||||||
99
readme.md
Normal file → Executable file
99
readme.md
Normal file → Executable file
|
|
@ -27,6 +27,8 @@
|
||||||
- No dependencies
|
- No dependencies
|
||||||
- Ability to nest styles
|
- Ability to nest styles
|
||||||
- [256/Truecolor color support](#256-and-truecolor-color-support)
|
- [256/Truecolor color support](#256-and-truecolor-color-support)
|
||||||
|
- [Themes](#themes) for consistent styling
|
||||||
|
- [Gradients](#gradients) for smooth color transitions
|
||||||
- Auto-detects color support
|
- Auto-detects color support
|
||||||
- Doesn't extend `String.prototype`
|
- Doesn't extend `String.prototype`
|
||||||
- Clean and focused
|
- Clean and focused
|
||||||
|
|
@ -87,18 +89,6 @@ log(chalk.rgb(123, 45, 67).underline('Underlined reddish color'));
|
||||||
log(chalk.hex('#DEADED').bold('Bold gray!'));
|
log(chalk.hex('#DEADED').bold('Bold gray!'));
|
||||||
```
|
```
|
||||||
|
|
||||||
Easily define your own themes:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import chalk from 'chalk';
|
|
||||||
|
|
||||||
const error = chalk.bold.red;
|
|
||||||
const warning = chalk.hex('#FFA500'); // Orange color
|
|
||||||
|
|
||||||
console.log(error('Error!'));
|
|
||||||
console.log(warning('Warning!'));
|
|
||||||
```
|
|
||||||
|
|
||||||
Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args):
|
Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|
@ -109,6 +99,40 @@ console.log(chalk.green('Hello %s'), name);
|
||||||
//=> 'Hello Sindre'
|
//=> 'Hello Sindre'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
Define a theme with multiple styles for consistent coloring:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
error: chalk.bold.red,
|
||||||
|
success: chalk.green,
|
||||||
|
warning: chalk.hex('#FFA500'), // Orange
|
||||||
|
info: chalk.blue,
|
||||||
|
title: chalk.cyan.bold.underline,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(themedChalk.error('Error!'));
|
||||||
|
console.log(themedChalk.success('Success!'));
|
||||||
|
console.log(themedChalk.warning('Warning!'));
|
||||||
|
console.log(themedChalk.info('Info'));
|
||||||
|
console.log(themedChalk.title('Title'));
|
||||||
|
```
|
||||||
|
|
||||||
|
Themes work with all Chalk features including gradients and chaining:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
rainbow: chalk.gradient('#ff0000', '#00ff00', '#0000ff'),
|
||||||
|
important: chalk.red.bold.underline,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(themedChalk.rainbow('Rainbow text'));
|
||||||
|
console.log(themedChalk.important.bgWhite('Important message'));
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### chalk.`<style>[.<style>...](string, [string...])`
|
### chalk.`<style>[.<style>...](string, [string...])`
|
||||||
|
|
@ -119,6 +143,26 @@ Chain [styles](#styles) and call the last one as a method with a string argument
|
||||||
|
|
||||||
Multiple arguments will be separated by space.
|
Multiple arguments will be separated by space.
|
||||||
|
|
||||||
|
### chalk.theme(theme)
|
||||||
|
|
||||||
|
Create a themed Chalk instance with custom styles.
|
||||||
|
|
||||||
|
```js
|
||||||
|
chalk.theme({
|
||||||
|
error: chalk.red.bold,
|
||||||
|
success: chalk.green,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### chalk.gradient(...colors)
|
||||||
|
|
||||||
|
Apply a color gradient to text.
|
||||||
|
|
||||||
|
```js
|
||||||
|
chalk.gradient('#ff0000', '#0000ff')('Hello');
|
||||||
|
chalk.gradient([255, 0, 0], [0, 0, 255])('World');
|
||||||
|
```
|
||||||
|
|
||||||
### chalk.level
|
### chalk.level
|
||||||
|
|
||||||
Specifies the level of color support.
|
Specifies the level of color support.
|
||||||
|
|
@ -243,6 +287,37 @@ The following color models can be used:
|
||||||
- [`hex`](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) - Example: `chalk.hex('#FF8800').bold('Orange!')`
|
- [`hex`](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) - Example: `chalk.hex('#FF8800').bold('Orange!')`
|
||||||
- [`ansi256`](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) - Example: `chalk.bgAnsi256(194)('Honeydew, more or less')`
|
- [`ansi256`](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) - Example: `chalk.bgAnsi256(194)('Honeydew, more or less')`
|
||||||
|
|
||||||
|
## Gradients
|
||||||
|
|
||||||
|
Apply smooth color gradients to text:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
// Two-color gradient
|
||||||
|
console.log(chalk.gradient('#ff0000', '#0000ff')('Hello World'));
|
||||||
|
|
||||||
|
// RGB arrays
|
||||||
|
console.log(chalk.gradient([255, 0, 0], [0, 255, 0], [0, 0, 255])('Rainbow'));
|
||||||
|
|
||||||
|
// Multiple colors
|
||||||
|
console.log(chalk.gradient('#ff0000', '#ffff00', '#00ff00', '#00ffff', '#0000ff')('Spectrum'));
|
||||||
|
```
|
||||||
|
|
||||||
|
Gradients work with all Chalk features:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Combine with other styles
|
||||||
|
console.log(chalk.gradient('#ff0080', '#8000ff').bold('Bold gradient'));
|
||||||
|
|
||||||
|
// Use in themes
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
rainbow: chalk.gradient('#ff0000', '#00ff00', '#0000ff'),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(themedChalk.rainbow('Themed rainbow'));
|
||||||
|
```
|
||||||
|
|
||||||
## Browser support
|
## Browser support
|
||||||
|
|
||||||
Since Chrome 69, ANSI escape codes are natively supported in the developer console.
|
Since Chrome 69, ANSI escape codes are natively supported in the developer console.
|
||||||
|
|
|
||||||
36
source/index.d.ts
vendored
Normal file → Executable file
36
source/index.d.ts
vendored
Normal file → Executable file
|
|
@ -121,6 +121,21 @@ export interface ChalkInstance {
|
||||||
*/
|
*/
|
||||||
bgAnsi256: (index: number) => this;
|
bgAnsi256: (index: number) => this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a gradient between colors and apply it to the text.
|
||||||
|
|
||||||
|
@param colors - Array of colors (hex strings or RGB arrays) to gradient between.
|
||||||
|
|
||||||
|
@example
|
||||||
|
```
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
chalk.gradient('#ff0000', '#0000ff')('Gradient text');
|
||||||
|
chalk.gradient([255, 0, 0], [0, 0, 255])('RGB gradient');
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
gradient: (...colors: Array<string | [number, number, number]>) => this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Modifier: Reset the current style.
|
Modifier: Reset the current style.
|
||||||
*/
|
*/
|
||||||
|
|
@ -228,6 +243,27 @@ export interface ChalkInstance {
|
||||||
readonly bgMagentaBright: this;
|
readonly bgMagentaBright: this;
|
||||||
readonly bgCyanBright: this;
|
readonly bgCyanBright: this;
|
||||||
readonly bgWhiteBright: this;
|
readonly bgWhiteBright: this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a themed Chalk instance with custom styles.
|
||||||
|
|
||||||
|
@param theme - An object where keys are style names and values are Chalk instances.
|
||||||
|
|
||||||
|
@example
|
||||||
|
```
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
error: chalk.red.bold,
|
||||||
|
success: chalk.green,
|
||||||
|
warning: chalk.yellow,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(themedChalk.error('This is an error'));
|
||||||
|
console.log(themedChalk.success('This is a success'));
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
theme<T extends Record<string, this>>(theme: T): this & T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
61
source/index.js
Normal file → Executable file
61
source/index.js
Normal file → Executable file
|
|
@ -3,6 +3,8 @@ import supportsColor from '#supports-color';
|
||||||
import { // eslint-disable-line import/order
|
import { // eslint-disable-line import/order
|
||||||
stringReplaceAll,
|
stringReplaceAll,
|
||||||
stringEncaseCRLFWithFirstIndex,
|
stringEncaseCRLFWithFirstIndex,
|
||||||
|
createGradientStyler,
|
||||||
|
applyGradient,
|
||||||
} from './utilities.js';
|
} from './utilities.js';
|
||||||
|
|
||||||
const {stdout: stdoutColor, stderr: stderrColor} = supportsColor;
|
const {stdout: stdoutColor, stderr: stderrColor} = supportsColor;
|
||||||
|
|
@ -71,6 +73,18 @@ styles.visible = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
styles.theme = {
|
||||||
|
value(theme) {
|
||||||
|
const themed = createBuilder(this, this[STYLER], this[IS_EMPTY]);
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(theme)) {
|
||||||
|
Object.defineProperty(themed, key, {value});
|
||||||
|
}
|
||||||
|
|
||||||
|
return themed;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const getModelAnsi = (model, level, type, ...arguments_) => {
|
const getModelAnsi = (model, level, type, ...arguments_) => {
|
||||||
if (model === 'rgb') {
|
if (model === 'rgb') {
|
||||||
if (level === 'ansi16m') {
|
if (level === 'ansi16m') {
|
||||||
|
|
@ -116,6 +130,12 @@ for (const model of usedModels) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
styles.gradient = {
|
||||||
|
get() {
|
||||||
|
return (...colors) => createBuilder(this, createGradientStyler(colors), this[IS_EMPTY]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const proto = Object.defineProperties(() => {}, {
|
const proto = Object.defineProperties(() => {}, {
|
||||||
...styles,
|
...styles,
|
||||||
level: {
|
level: {
|
||||||
|
|
@ -176,7 +196,39 @@ const applyStyle = (self, string) => {
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {openAll, closeAll} = styler;
|
// Find gradient styler in the chain
|
||||||
|
let gradientStyler = null;
|
||||||
|
let current = styler;
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
if (current.gradient) {
|
||||||
|
gradientStyler = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
let openAll;
|
||||||
|
let closeAll;
|
||||||
|
if (gradientStyler) {
|
||||||
|
// Build openAll/closeAll excluding gradient stylers
|
||||||
|
openAll = '';
|
||||||
|
closeAll = '';
|
||||||
|
current = styler;
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
if (!current.gradient) {
|
||||||
|
openAll = current.open + openAll;
|
||||||
|
closeAll += current.close;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
({openAll, closeAll} = styler);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.includes('\u001B')) {
|
if (string.includes('\u001B')) {
|
||||||
while (styler !== undefined) {
|
while (styler !== undefined) {
|
||||||
// Replace any instances already present with a re-opening code
|
// Replace any instances already present with a re-opening code
|
||||||
|
|
@ -188,6 +240,13 @@ const applyStyle = (self, string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply gradient if present
|
||||||
|
if (gradientStyler) {
|
||||||
|
string = applyGradient(string, gradientStyler.colors, self.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can move both next actions out of loop, because remaining actions in loop won't have
|
||||||
|
|
||||||
// We can move both next actions out of loop, because remaining actions in loop won't have
|
// We can move both next actions out of loop, because remaining actions in loop won't have
|
||||||
// any/visible effect on parts we add here. Close the styling before a linebreak and reopen
|
// any/visible effect on parts we add here. Close the styling before a linebreak and reopen
|
||||||
// after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92
|
// after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92
|
||||||
|
|
|
||||||
55
source/utilities.js
Normal file → Executable file
55
source/utilities.js
Normal file → Executable file
|
|
@ -1,3 +1,5 @@
|
||||||
|
import ansiStyles from '#ansi-styles';
|
||||||
|
|
||||||
// TODO: When targeting Node.js 16, use `String.prototype.replaceAll`.
|
// TODO: When targeting Node.js 16, use `String.prototype.replaceAll`.
|
||||||
export function stringReplaceAll(string, substring, replacer) {
|
export function stringReplaceAll(string, substring, replacer) {
|
||||||
let index = string.indexOf(substring);
|
let index = string.indexOf(substring);
|
||||||
|
|
@ -31,3 +33,56 @@ export function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
||||||
returnValue += string.slice(endIndex);
|
returnValue += string.slice(endIndex);
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const interpolateRgb = (color1, color2, factor) => [
|
||||||
|
Math.round(color1[0] + ((color2[0] - color1[0]) * factor)),
|
||||||
|
Math.round(color1[1] + ((color2[1] - color1[1]) * factor)),
|
||||||
|
Math.round(color1[2] + ((color2[2] - color1[2]) * factor)),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const createGradientStyler = colors => ({
|
||||||
|
gradient: true,
|
||||||
|
colors: colors.map(color => {
|
||||||
|
if (typeof color === 'string') {
|
||||||
|
return ansiStyles.hexToRgb(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color; // Assume [r, g, b]
|
||||||
|
}),
|
||||||
|
open: '',
|
||||||
|
close: ansiStyles.color.close,
|
||||||
|
openAll: '',
|
||||||
|
closeAll: ansiStyles.color.close,
|
||||||
|
parent: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const applyGradient = (string, colors, level) => {
|
||||||
|
if (colors.length < 2 || !string) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chars = [...string];
|
||||||
|
let result = '';
|
||||||
|
const segments = colors.length - 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < chars.length; i++) {
|
||||||
|
const factor = i / (chars.length - 1 || 1);
|
||||||
|
const segmentIndex = Math.min(Math.floor(factor * segments), segments - 1);
|
||||||
|
const localFactor = (factor * segments) - segmentIndex;
|
||||||
|
const color = interpolateRgb(colors[segmentIndex], colors[segmentIndex + 1], localFactor);
|
||||||
|
|
||||||
|
let code;
|
||||||
|
if (level === 3) {
|
||||||
|
code = ansiStyles.color.ansi16m(...color);
|
||||||
|
} else if (level === 2) {
|
||||||
|
code = ansiStyles.color.ansi256(ansiStyles.rgbToAnsi256(...color));
|
||||||
|
} else {
|
||||||
|
code = ansiStyles.color.ansi(ansiStyles.rgbToAnsi(...color));
|
||||||
|
}
|
||||||
|
|
||||||
|
result += code + chars[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
result += ansiStyles.color.close;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
|
||||||
172
test/gradient.js
Normal file
172
test/gradient.js
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
import test from 'ava';
|
||||||
|
import chalk from '../source/index.js';
|
||||||
|
|
||||||
|
chalk.level = 3;
|
||||||
|
|
||||||
|
test('gradient: applies color gradient to text', t => {
|
||||||
|
// Test hex gradient
|
||||||
|
const gradientHex = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
t.is(gradientHex('ab'), '\u001B[38;2;255;0;0ma\u001B[38;2;0;0;255mb\u001B[39m');
|
||||||
|
|
||||||
|
// Test RGB array gradient
|
||||||
|
const gradientRgb = chalk.gradient([255, 0, 0], [0, 0, 255]);
|
||||||
|
t.is(gradientRgb('ab'), '\u001B[38;2;255;0;0ma\u001B[38;2;0;0;255mb\u001B[39m');
|
||||||
|
|
||||||
|
// Test multi-color gradient
|
||||||
|
const multiGradient = chalk.gradient('#ff0000', '#00ff00', '#0000ff');
|
||||||
|
const result = multiGradient('abc');
|
||||||
|
t.true(result.includes('\u001B[38;2;255;0;0ma'));
|
||||||
|
t.true(result.includes('\u001B[38;2;0;255;0mb'));
|
||||||
|
t.true(result.includes('\u001B[38;2;0;0;255mc'));
|
||||||
|
t.true(result.endsWith('\u001B[39m'));
|
||||||
|
|
||||||
|
// Test gradient with other styles
|
||||||
|
const gradientBold = chalk.gradient('#ff0000', '#0000ff').bold;
|
||||||
|
t.is(gradientBold('ab'), '\u001B[1m\u001B[38;2;255;0;0ma\u001B[38;2;0;0;255mb\u001B[39m\u001B[22m');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: handles edge cases', t => {
|
||||||
|
// Single character
|
||||||
|
const singleChar = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
t.is(singleChar('a'), '\u001B[38;2;255;0;0ma\u001B[39m');
|
||||||
|
|
||||||
|
// Empty string
|
||||||
|
const empty = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
t.is(empty(''), '');
|
||||||
|
|
||||||
|
// Very long text
|
||||||
|
const longText = 'a'.repeat(100);
|
||||||
|
const longGradient = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
const longResult = longGradient(longText);
|
||||||
|
t.true(longResult.startsWith('\u001B[38;2;255;0;0ma'));
|
||||||
|
t.true(longResult.endsWith('a\u001B[39m'));
|
||||||
|
t.true(longResult.includes('\u001B[38;2;'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: supports different color formats', t => {
|
||||||
|
// Mixed hex and RGB
|
||||||
|
const mixed = chalk.gradient('#ff0000', [0, 255, 0], '#0000ff');
|
||||||
|
const mixedResult = mixed('abc');
|
||||||
|
t.true(mixedResult.includes('\u001B[38;2;255;0;0ma'));
|
||||||
|
t.true(mixedResult.includes('\u001B[38;2;0;255;0mb'));
|
||||||
|
t.true(mixedResult.includes('\u001B[38;2;0;0;255mc'));
|
||||||
|
|
||||||
|
// Short hex codes
|
||||||
|
const shortHex = chalk.gradient('#f00', '#00f');
|
||||||
|
t.is(shortHex('ab'), '\u001B[38;2;255;0;0ma\u001B[38;2;0;0;255mb\u001B[39m');
|
||||||
|
|
||||||
|
// Uppercase hex
|
||||||
|
const upperHex = chalk.gradient('#FF0000', '#0000FF');
|
||||||
|
t.is(upperHex('ab'), '\u001B[38;2;255;0;0ma\u001B[38;2;0;0;255mb\u001B[39m');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: works with multiple style chaining', t => {
|
||||||
|
// Bold + underline + gradient
|
||||||
|
const complex = chalk.gradient('#ff0000', '#0000ff').bold.underline;
|
||||||
|
const complexResult = complex('ab');
|
||||||
|
t.true(complexResult.includes('\u001B[1m')); // Bold
|
||||||
|
t.true(complexResult.includes('\u001B[4m')); // Underline
|
||||||
|
t.true(complexResult.includes('\u001B[38;2;255;0;0ma'));
|
||||||
|
t.true(complexResult.includes('\u001B[38;2;0;0;255mb'));
|
||||||
|
t.true(complexResult.includes('\u001B[24m')); // Underline off
|
||||||
|
t.true(complexResult.includes('\u001B[22m')); // Bold off
|
||||||
|
|
||||||
|
// Multiple gradients (nested)
|
||||||
|
const nested = chalk.red.bold(chalk.gradient('#ff0000', '#0000ff')('ab'));
|
||||||
|
const nestedResult = nested;
|
||||||
|
t.true(nestedResult.includes('\u001B[31m')); // Red
|
||||||
|
t.true(nestedResult.includes('\u001B[1m')); // Bold
|
||||||
|
t.true(nestedResult.includes('\u001B[38;2;255;0;0ma'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: handles different color levels', t => {
|
||||||
|
// Level 0 (no colors)
|
||||||
|
const level0 = new chalk.constructor({level: 0}).gradient('#ff0000', '#0000ff');
|
||||||
|
t.is(level0('ab'), 'ab');
|
||||||
|
|
||||||
|
// Level 1 (basic colors)
|
||||||
|
const level1 = new chalk.constructor({level: 1}).gradient('#ff0000', '#0000ff');
|
||||||
|
const level1Result = level1('ab');
|
||||||
|
t.true(level1Result.includes('\u001B[91m')); // Bright red equivalent
|
||||||
|
t.true(level1Result.includes('\u001B[94m')); // Bright blue equivalent
|
||||||
|
|
||||||
|
// Level 2 (256 colors)
|
||||||
|
const level2 = new chalk.constructor({level: 2}).gradient('#ff0000', '#0000ff');
|
||||||
|
const level2Result = level2('ab');
|
||||||
|
t.true(level2Result.includes('\u001B[38;5;')); // 256 color codes
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: works with themes', t => {
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
rainbow: chalk.gradient('#ff0000', '#00ff00', '#0000ff'),
|
||||||
|
ocean: chalk.gradient('#000080', '#008080', '#00ffff'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rainbowResult = themedChalk.rainbow('abc');
|
||||||
|
t.true(rainbowResult.includes('\u001B[38;2;255;0;0ma'));
|
||||||
|
t.true(rainbowResult.includes('\u001B[38;2;0;255;0mb'));
|
||||||
|
t.true(rainbowResult.includes('\u001B[38;2;0;0;255mc'));
|
||||||
|
|
||||||
|
const oceanResult = themedChalk.ocean('xyz');
|
||||||
|
t.true(oceanResult.includes('\u001B[38;2;')); // Should have gradient colors
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: handles special characters and unicode', t => {
|
||||||
|
// Unicode characters
|
||||||
|
const unicode = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
const unicodeResult = unicode('🚀🌟⭐');
|
||||||
|
t.true(unicodeResult.includes('🚀'));
|
||||||
|
t.true(unicodeResult.includes('🌟'));
|
||||||
|
t.true(unicodeResult.includes('⭐'));
|
||||||
|
t.true(unicodeResult.includes('\u001B[38;2;'));
|
||||||
|
|
||||||
|
// Numbers and special chars
|
||||||
|
const special = chalk.gradient('#ff0000', '#0000ff');
|
||||||
|
const specialResult = special('123!@#');
|
||||||
|
t.true(specialResult.includes('1'));
|
||||||
|
t.true(specialResult.includes('2'));
|
||||||
|
t.true(specialResult.includes('3'));
|
||||||
|
t.true(specialResult.includes('!'));
|
||||||
|
t.true(specialResult.includes('@'));
|
||||||
|
t.true(specialResult.includes('#'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: supports background gradients', t => {
|
||||||
|
// Note: This tests that gradient works with background colors applied separately
|
||||||
|
const bgGradient = chalk.bgRed(chalk.gradient('#ff0000', '#0000ff')('ab'));
|
||||||
|
const bgResult = bgGradient;
|
||||||
|
t.true(bgResult.includes('\u001B[41m')); // Red background
|
||||||
|
t.true(bgResult.includes('\u001B[38;2;255;0;0ma')); // Gradient text
|
||||||
|
t.true(bgResult.includes('\u001B[49m')); // Reset background
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: handles single color edge case', t => {
|
||||||
|
// Single color doesn't apply gradient (returns plain text)
|
||||||
|
const single = chalk.gradient('#ff0000');
|
||||||
|
const singleResult = single('abc');
|
||||||
|
t.is(singleResult, 'abc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gradient: maintains text content integrity', t => {
|
||||||
|
const original = 'Hello, World! 🌍 This is a test with special chars: !@#$%^&*()';
|
||||||
|
const gradient = chalk.gradient('#ff0000', '#0000ff', '#00ff00');
|
||||||
|
const result = gradient(original);
|
||||||
|
|
||||||
|
// Remove ANSI codes and check content is preserved
|
||||||
|
let cleanResult = result;
|
||||||
|
let index = cleanResult.indexOf('\u001B[');
|
||||||
|
while (index !== -1) {
|
||||||
|
const endIndex = cleanResult.indexOf('m', index);
|
||||||
|
if (endIndex !== -1) {
|
||||||
|
cleanResult = cleanResult.slice(0, index) + cleanResult.slice(endIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
index = cleanResult.indexOf('\u001B[', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
t.is(cleanResult, original);
|
||||||
|
|
||||||
|
// Ensure it has gradient codes
|
||||||
|
t.true(result.includes('\u001B[38;2;'));
|
||||||
|
t.true(result.endsWith('\u001B[39m'));
|
||||||
|
});
|
||||||
22
test/theme.js
Normal file
22
test/theme.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import test from 'ava';
|
||||||
|
import chalk from '../source/index.js';
|
||||||
|
|
||||||
|
chalk.level = 3;
|
||||||
|
|
||||||
|
test('theme: creates a themed chalk instance with custom styles', t => {
|
||||||
|
const themedChalk = chalk.theme({
|
||||||
|
error: chalk.red.bold,
|
||||||
|
success: chalk.green,
|
||||||
|
warning: chalk.yellow.underline,
|
||||||
|
});
|
||||||
|
|
||||||
|
t.is(themedChalk.error('This is an error'), '\u001B[31m\u001B[1mThis is an error\u001B[22m\u001B[39m');
|
||||||
|
t.is(themedChalk.success('This is a success'), '\u001B[32mThis is a success\u001B[39m');
|
||||||
|
t.is(themedChalk.warning('This is a warning'), '\u001B[33m\u001B[4mThis is a warning\u001B[24m\u001B[39m');
|
||||||
|
|
||||||
|
// Ensure themed chalk still has original styles
|
||||||
|
t.is(themedChalk.blue('Blue text'), '\u001B[34mBlue text\u001B[39m');
|
||||||
|
|
||||||
|
// Ensure chaining works on themed
|
||||||
|
t.is(themedChalk.error.bgWhite('Error on white'), '\u001B[31m\u001B[1m\u001B[47mError on white\u001B[49m\u001B[22m\u001B[39m');
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue