Launch Week Day 4: Introducing Data Exports.Learn More
Socket
Book a DemoSign in
Socket

gradient-parser

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

gradient-parser - npm Package Compare versions

Comparing version
1.0.2
to
1.1.0
+27
.github/workflows/ci.yml
name: Node.js CI
on:
push:
branches:
- master # Trigger workflow on pushes to the master branch
pull_request: # Trigger workflow on any pull request
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x] # Test on multiple Node.js versions
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm' # Enable caching for npm dependencies
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
const fs = require('fs');
const path = require('path');
const esbuild = require('esbuild');
// Determine which build to run based on command-line args
const args = process.argv.slice(2);
const buildNode = args.includes('--node') || !args.length;
const buildWeb = args.includes('--web') || !args.length;
const minify = args.includes('--minify');
// Helper to ensure directories exist
const ensureDir = (dirPath) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
};
// Helper function for minification
const minifyCode = async (code, outputFile) => {
const result = await esbuild.transform(code, {
minify: true,
target: 'es6',
});
fs.writeFileSync(outputFile, result.code);
return result.code;
};
// Make sure the build directory exists
ensureDir(path.join(__dirname, 'build'));
// Async function to handle potential async operations like minification
async function build() {
// Build for Node.js
if (buildNode) {
console.log('Building node.js bundle...');
// Read the source files
const stringifyContent = fs.readFileSync(path.join(__dirname, 'lib', 'stringify.js'), 'utf8');
const parserContent = fs.readFileSync(path.join(__dirname, 'lib', 'parser.js'), 'utf8');
const indexContent = fs.readFileSync(path.join(__dirname, 'index.js'), 'utf8');
// Concatenate the files
const nodeBundle = `${stringifyContent}\n${parserContent}\n${indexContent}`;
// Write the bundle
const outputPath = path.join(__dirname, 'build', 'node.js');
if (minify) {
await minifyCode(nodeBundle, outputPath);
console.log('✓ Node.js bundle created and minified successfully');
} else {
fs.writeFileSync(outputPath, nodeBundle);
console.log('✓ Node.js bundle created successfully');
}
}
// Build for Web
if (buildWeb) {
console.log('Building web.js bundle...');
// Read the source files
const webifyContent = fs.readFileSync(path.join(__dirname, 'webify.js'), 'utf8');
const parserContent = fs.readFileSync(path.join(__dirname, 'lib', 'parser.js'), 'utf8');
const stringifyContent = fs.readFileSync(path.join(__dirname, 'lib', 'stringify.js'), 'utf8');
// Concatenate the files
const webBundle = `${webifyContent}\n${parserContent}\n${stringifyContent}`;
// Write the bundle
const outputPath = path.join(__dirname, 'build', 'web.js');
if (minify) {
await minifyCode(webBundle, outputPath);
console.log('✓ Web bundle created and minified successfully');
} else {
fs.writeFileSync(outputPath, webBundle);
console.log('✓ Web bundle created successfully');
}
}
// If no arguments were provided, we built both bundles
if (!args.length) {
console.log('✓ All bundles built successfully');
}
}
// Execute the build
console.log(`Building with options: ${minify ? 'minify' : 'no minify'} ${buildNode ? 'node' : ''} ${buildWeb ? 'web' : ''}`)
build().catch(err => {
console.error('Build failed:', err);
process.exit(1);
});
+103
-5

@@ -109,2 +109,14 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved.

'visit_hsl': function(node) {
return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node);
},
'visit_hsla': function(node) {
return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node);
},
'visit_var': function(node) {
return visitor.visit_color('var(' + node.value + ')', node);
},
'visit_color': function(resultColor, node) {

@@ -142,2 +154,9 @@ var result = resultColor,

'visit_object': function(obj) {
if (obj.width && obj.height) {
return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height);
}
return '';
},
'visit': function(element) {

@@ -151,2 +170,4 @@ if (!element) {

return visitor.visit_array(element, result);
} else if (typeof element === 'object' && !element.type) {
return visitor.visit_object(element);
} else if (element.type) {

@@ -184,3 +205,3 @@ var nodeVisitor = visitor['visit_' + element.type];

repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,

@@ -192,2 +213,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i,

angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/,
startCall: /^\(/,

@@ -200,3 +222,7 @@ endCall: /^\)/,

rgbaColor: /^rgba/i,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
varColor: /^var/i,
variableName: /^(--[a-zA-Z0-9-,\s\#]+)/,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
hslColor: /^hsl/i,
hslaColor: /^hsla/i,
};

@@ -294,3 +320,4 @@

function matchAngle() {
return match('angular', tokens.angleValue, 1);
return match('angular', tokens.angleValue, 1) ||
match('angular', tokens.radianValue, 1);
}

@@ -363,3 +390,3 @@

if (ellipse) {
ellipse.style = matchDistance() || matchExtentKeyword();
ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword();
}

@@ -436,4 +463,7 @@

return matchHexColor() ||
matchHSLAColor() ||
matchHSLColor() ||
matchRGBAColor() ||
matchRGBColor() ||
matchVarColor() ||
matchLiteralColor();

@@ -468,2 +498,66 @@ }

function matchVarColor() {
return matchCall(tokens.varColor, function () {
return {
type: 'var',
value: matchVariableName()
};
});
}
function matchHSLColor() {
return matchCall(tokens.hslColor, function() {
// Check for percentage before trying to parse the hue
var lookahead = scan(tokens.percentageValue);
if (lookahead) {
error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage');
}
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSL');
}
return {
type: 'hsl',
value: [hue, sat, light]
};
});
}
function matchHSLAColor() {
return matchCall(tokens.hslaColor, function() {
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
scan(tokens.comma);
var alpha = matchNumber();
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSLA');
}
return {
type: 'hsla',
value: [hue, sat, light, alpha]
};
});
}
function matchPercentage() {
var captures = scan(tokens.percentageValue);
return captures ? captures[1] : null;
}
function matchVariableName() {
return scan(tokens.variableName)[1];
}
function matchNumber() {

@@ -520,3 +614,7 @@ return scan(tokens.number)[1];

return function(code) {
input = code.toString();
input = code.toString().trim();
// Remove trailing semicolon if present
if (input.endsWith(';')) {
input = input.slice(0, -1);
}
return getAST();

@@ -523,0 +621,0 @@ };

@@ -16,3 +16,3 @@ var GradientParser = (window.GradientParser || {});

repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,

@@ -24,2 +24,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i,

angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/,
startCall: /^\(/,

@@ -32,3 +33,7 @@ endCall: /^\)/,

rgbaColor: /^rgba/i,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
varColor: /^var/i,
variableName: /^(--[a-zA-Z0-9-,\s\#]+)/,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
hslColor: /^hsl/i,
hslaColor: /^hsla/i,
};

@@ -126,3 +131,4 @@

function matchAngle() {
return match('angular', tokens.angleValue, 1);
return match('angular', tokens.angleValue, 1) ||
match('angular', tokens.radianValue, 1);
}

@@ -195,3 +201,3 @@

if (ellipse) {
ellipse.style = matchDistance() || matchExtentKeyword();
ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword();
}

@@ -268,4 +274,7 @@

return matchHexColor() ||
matchHSLAColor() ||
matchHSLColor() ||
matchRGBAColor() ||
matchRGBColor() ||
matchVarColor() ||
matchLiteralColor();

@@ -300,2 +309,66 @@ }

function matchVarColor() {
return matchCall(tokens.varColor, function () {
return {
type: 'var',
value: matchVariableName()
};
});
}
function matchHSLColor() {
return matchCall(tokens.hslColor, function() {
// Check for percentage before trying to parse the hue
var lookahead = scan(tokens.percentageValue);
if (lookahead) {
error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage');
}
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSL');
}
return {
type: 'hsl',
value: [hue, sat, light]
};
});
}
function matchHSLAColor() {
return matchCall(tokens.hslaColor, function() {
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
scan(tokens.comma);
var alpha = matchNumber();
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSLA');
}
return {
type: 'hsla',
value: [hue, sat, light, alpha]
};
});
}
function matchPercentage() {
var captures = scan(tokens.percentageValue);
return captures ? captures[1] : null;
}
function matchVariableName() {
return scan(tokens.variableName)[1];
}
function matchNumber() {

@@ -352,3 +425,7 @@ return scan(tokens.number)[1];

return function(code) {
input = code.toString();
input = code.toString().trim();
// Remove trailing semicolon if present
if (input.endsWith(';')) {
input = input.slice(0, -1);
}
return getAST();

@@ -466,2 +543,14 @@ };

'visit_hsl': function(node) {
return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node);
},
'visit_hsla': function(node) {
return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node);
},
'visit_var': function(node) {
return visitor.visit_color('var(' + node.value + ')', node);
},
'visit_color': function(resultColor, node) {

@@ -499,2 +588,9 @@ var result = resultColor,

'visit_object': function(obj) {
if (obj.width && obj.height) {
return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height);
}
return '';
},
'visit': function(element) {

@@ -508,2 +604,4 @@ if (!element) {

return visitor.visit_array(element, result);
} else if (typeof element === 'object' && !element.type) {
return visitor.visit_object(element);
} else if (element.type) {

@@ -510,0 +608,0 @@ var nodeVisitor = visitor['visit_' + element.type];

@@ -14,3 +14,3 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved.

repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i,
sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,
extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/,

@@ -22,2 +22,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i,

angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/,
radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/,
startCall: /^\(/,

@@ -30,3 +31,7 @@ endCall: /^\)/,

rgbaColor: /^rgba/i,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/
varColor: /^var/i,
variableName: /^(--[a-zA-Z0-9-,\s\#]+)/,
number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/,
hslColor: /^hsl/i,
hslaColor: /^hsla/i,
};

@@ -124,3 +129,4 @@

function matchAngle() {
return match('angular', tokens.angleValue, 1);
return match('angular', tokens.angleValue, 1) ||
match('angular', tokens.radianValue, 1);
}

@@ -193,3 +199,3 @@

if (ellipse) {
ellipse.style = matchDistance() || matchExtentKeyword();
ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword();
}

@@ -266,4 +272,7 @@

return matchHexColor() ||
matchHSLAColor() ||
matchHSLColor() ||
matchRGBAColor() ||
matchRGBColor() ||
matchVarColor() ||
matchLiteralColor();

@@ -298,2 +307,66 @@ }

function matchVarColor() {
return matchCall(tokens.varColor, function () {
return {
type: 'var',
value: matchVariableName()
};
});
}
function matchHSLColor() {
return matchCall(tokens.hslColor, function() {
// Check for percentage before trying to parse the hue
var lookahead = scan(tokens.percentageValue);
if (lookahead) {
error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage');
}
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSL');
}
return {
type: 'hsl',
value: [hue, sat, light]
};
});
}
function matchHSLAColor() {
return matchCall(tokens.hslaColor, function() {
var hue = matchNumber();
scan(tokens.comma);
var captures = scan(tokens.percentageValue);
var sat = captures ? captures[1] : null;
scan(tokens.comma);
captures = scan(tokens.percentageValue);
var light = captures ? captures[1] : null;
scan(tokens.comma);
var alpha = matchNumber();
if (!sat || !light) {
error('Expected percentage value for saturation and lightness in HSLA');
}
return {
type: 'hsla',
value: [hue, sat, light, alpha]
};
});
}
function matchPercentage() {
var captures = scan(tokens.percentageValue);
return captures ? captures[1] : null;
}
function matchVariableName() {
return scan(tokens.variableName)[1];
}
function matchNumber() {

@@ -350,5 +423,9 @@ return scan(tokens.number)[1];

return function(code) {
input = code.toString();
input = code.toString().trim();
// Remove trailing semicolon if present
if (input.endsWith(';')) {
input = input.slice(0, -1);
}
return getAST();
};
})();

@@ -109,2 +109,14 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved.

'visit_hsl': function(node) {
return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node);
},
'visit_hsla': function(node) {
return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node);
},
'visit_var': function(node) {
return visitor.visit_color('var(' + node.value + ')', node);
},
'visit_color': function(resultColor, node) {

@@ -142,2 +154,9 @@ var result = resultColor,

'visit_object': function(obj) {
if (obj.width && obj.height) {
return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height);
}
return '';
},
'visit': function(element) {

@@ -151,2 +170,4 @@ if (!element) {

return visitor.visit_array(element, result);
} else if (typeof element === 'object' && !element.type) {
return visitor.visit_object(element);
} else if (element.type) {

@@ -153,0 +174,0 @@ var nodeVisitor = visitor['visit_' + element.type];

+11
-10
{
"name": "gradient-parser",
"version": "1.0.2",
"version": "1.1.0",
"description": "Parse CSS3 gradient definitions and return an AST.",

@@ -28,3 +28,9 @@ "author": {

"scripts": {
"test": "grunt"
"test": "mocha spec/**/*.js",
"build": "node build.js",
"build:node": "node build.js --node",
"build:web": "node build.js --web",
"build:minify": "node build.js --minify",
"start": "python -m SimpleHTTPServer 3000",
"prepublish": "npm run build"
},

@@ -37,10 +43,5 @@ "keywords": [

"devDependencies": {
"expect.js": "*",
"grunt": "*",
"grunt-browserify": "^3.0.1",
"grunt-complexity": "*",
"grunt-contrib-concat": "^0.5.0",
"grunt-contrib-uglify": "^0.5.1",
"grunt-mocha-test": "^0.11.0",
"mocha": "*"
"expect.js": "^0.3.1",
"esbuild": "^0.20.2",
"mocha": "^10.3.0"
},

@@ -47,0 +48,0 @@ "engines": {

# Gradient Parser
[![npm version](https://badge.fury.io/js/gradient-parser.svg)](https://badge.fury.io/js/gradient-parser)
## About

@@ -39,6 +41,66 @@

## Install Choices
- `bower install gradient-parser`
- [download the zip](https://github.com/rafaelcaricio/gradient-parser/archive/master.zip)
## Installation
Install via npm:
```bash
npm install gradient-parser
```
Import in Node.js:
```javascript
const gradient = require('gradient-parser');
```
For browser usage:
```html
<script src="node_modules/gradient-parser/build/web.js"></script>
```
Or [download the zip](https://github.com/rafaelcaricio/gradient-parser/archive/master.zip)
## Development
### Project Status
Gradient-parser has been modernized (as of v1.1.0):
- Removed Bower support in favor of npm exclusively
- Replaced Grunt with a modern build system using esbuild
- Added minification support
- Updated dependencies to specific versions
- Improved npm scripts for better developer experience
### Build
Gradient-parser uses a modern build system with esbuild for building and minification.
```bash
# Build both Node.js and web bundles
npm run build
# Build only Node.js bundle
npm run build:node
# Build only web bundle
npm run build:web
# Build minified bundles
npm run build:minify
```
### Testing
Run the test suite with:
```bash
npm test
```
### Starting a local server
You can run a simple HTTP server for development:
```bash
npm start
```
## API

@@ -134,3 +196,3 @@

Copyright (c) 2014 Rafael Caricio rafael@caricio.com
Copyright (c) 2014-2025 Rafael Caricio rafael@caricio.com

@@ -137,0 +199,0 @@ Permission is hereby granted, free of charge, to any person obtaining

@@ -137,3 +137,8 @@ 'use strict';

{type: 'angular', unparsedValue: '-145deg', value: '-145'},
{type: 'directional', unparsedValue: 'to left top', value: 'left top'}
{type: 'angular', unparsedValue: '1rad', value: '1'},
{type: 'directional', unparsedValue: 'to left top', value: 'left top'},
{type: 'directional', unparsedValue: 'to top left', value: 'top left'},
{type: 'directional', unparsedValue: 'to top right', value: 'top right'},
{type: 'directional', unparsedValue: 'to bottom left', value: 'bottom left'},
{type: 'directional', unparsedValue: 'to bottom right', value: 'bottom right'}
].forEach(function(orientation) {

@@ -159,3 +164,7 @@ describe('parse orientation ' + orientation.type, function() {

{type: 'rgb', unparsedValue: 'rgb(243, 226, 195)', value: ['243', '226', '195']},
{type: 'rgba', unparsedValue: 'rgba(243, 226, 195)', value: ['243', '226', '195']}
{type: 'rgba', unparsedValue: 'rgba(243, 226, 195)', value: ['243', '226', '195']},
{type: 'hsl', unparsedValue: 'hsl(120, 60%, 70%)', value: ['120', '60', '70']},
{type: 'hsla', unparsedValue: 'hsla(120, 60%, 70%, 0.3)', value: ['120', '60', '70', '0.3']},
{type: 'hsla', unparsedValue: 'hsla(240, 100%, 50%, 0.5)', value: ['240', '100', '50', '0.5']},
{type: 'var', unparsedValue: 'var(--color-red)', value: '--color-red'},
].forEach(function(color) {

@@ -174,2 +183,22 @@ describe('parse color type '+ color.type, function() {

});
describe('error cases for HSL/HSLA', function() {
it('should error on missing percentage for saturation', function() {
expect(function() {
gradients.parse('linear-gradient(hsl(120, 60, 70%))');
}).to.throwException(/Expected percentage value/);
});
it('should error on missing percentage for lightness', function() {
expect(function() {
gradients.parse('linear-gradient(hsl(120, 60%, 70))');
}).to.throwException(/Expected percentage value/);
});
it('should error on percentage for hue', function() {
expect(function() {
gradients.parse('linear-gradient(hsl(120%, 60%, 70%))');
}).to.throwException(/HSL hue value must be a number in degrees \(0-360\) or normalized \(-360 to 360\), not a percentage/);
});
});
});

@@ -214,4 +243,143 @@

});
it('should parse ellipse with dimensions and position', function() {
const gradient = 'repeating-radial-gradient(ellipse 40px 134px at 50% 96%, rgb(0, 165, 223) 0%, rgb(62, 20, 123) 6.6%)';
const ast = gradients.parse(gradient);
expect(ast[0].type).to.equal('repeating-radial-gradient');
expect(ast[0].orientation[0].type).to.equal('shape');
expect(ast[0].orientation[0].value).to.equal('ellipse');
// Check the style (size dimensions)
expect(ast[0].orientation[0].style.type).to.equal('position');
expect(ast[0].orientation[0].style.value.x.type).to.equal('px');
expect(ast[0].orientation[0].style.value.x.value).to.equal('40');
expect(ast[0].orientation[0].style.value.y.type).to.equal('px');
expect(ast[0].orientation[0].style.value.y.value).to.equal('134');
// Check the position
expect(ast[0].orientation[0].at.type).to.equal('position');
expect(ast[0].orientation[0].at.value.x.type).to.equal('%');
expect(ast[0].orientation[0].at.value.x.value).to.equal('50');
expect(ast[0].orientation[0].at.value.y.type).to.equal('%');
expect(ast[0].orientation[0].at.value.y.value).to.equal('96');
// Check the color stops
expect(ast[0].colorStops).to.have.length(2);
expect(ast[0].colorStops[0].type).to.equal('rgb');
expect(ast[0].colorStops[0].value).to.eql(['0', '165', '223']);
expect(ast[0].colorStops[0].length.type).to.equal('%');
expect(ast[0].colorStops[0].length.value).to.equal('0');
expect(ast[0].colorStops[1].type).to.equal('rgb');
expect(ast[0].colorStops[1].value).to.eql(['62', '20', '123']);
expect(ast[0].colorStops[1].length.type).to.equal('%');
expect(ast[0].colorStops[1].length.value).to.equal('6.6');
});
it('should parse full Pride flag gradient', function() {
const gradient = 'repeating-radial-gradient(ellipse 40px 134px at 50% 96%,rgb(0, 165, 223) 0%,rgb(62, 20, 123) 6.6%,rgb(226, 0, 121) 13.2%,rgb(223, 19, 44) 18.8%,rgb(243, 239, 21) 24.1%,rgb(0, 152, 71) 33.3%)';
const ast = gradients.parse(gradient);
expect(ast[0].type).to.equal('repeating-radial-gradient');
expect(ast[0].orientation[0].type).to.equal('shape');
expect(ast[0].orientation[0].value).to.equal('ellipse');
// Check dimensions and position
expect(ast[0].orientation[0].style.type).to.equal('position');
expect(ast[0].orientation[0].at.type).to.equal('position');
// Verify all color stops are present (Pride flag colors)
expect(ast[0].colorStops).to.have.length(6);
// Check the first and last color stops
expect(ast[0].colorStops[0].type).to.equal('rgb');
expect(ast[0].colorStops[0].value).to.eql(['0', '165', '223']);
expect(ast[0].colorStops[5].type).to.equal('rgb');
expect(ast[0].colorStops[5].value).to.eql(['0', '152', '71']);
expect(ast[0].colorStops[5].length.type).to.equal('%');
expect(ast[0].colorStops[5].length.value).to.equal('33.3');
});
});
describe('parse gradient strings with trailing semicolons', function() {
it('should parse linear-gradient with trailing semicolon', function() {
const inputWithSemicolon = 'linear-gradient(red, blue);';
const ast = gradients.parse(inputWithSemicolon);
expect(ast[0].type).to.equal('linear-gradient');
expect(ast[0].colorStops).to.have.length(2);
expect(ast[0].colorStops[0].value).to.equal('red');
expect(ast[0].colorStops[1].value).to.equal('blue');
});
it('should parse radial-gradient with trailing semicolon', function() {
const inputWithSemicolon = 'radial-gradient(circle, red, blue);';
const ast = gradients.parse(inputWithSemicolon);
expect(ast[0].type).to.equal('radial-gradient');
expect(ast[0].colorStops).to.have.length(2);
expect(ast[0].colorStops[0].value).to.equal('red');
expect(ast[0].colorStops[1].value).to.equal('blue');
});
it('should parse complex gradient with trailing semicolon', function() {
const inputWithSemicolon = 'linear-gradient(to right, rgb(22, 234, 174) 0%, rgb(126, 32, 207) 100%);';
const ast = gradients.parse(inputWithSemicolon);
expect(ast[0].type).to.equal('linear-gradient');
expect(ast[0].orientation.type).to.equal('directional');
expect(ast[0].orientation.value).to.equal('right');
expect(ast[0].colorStops).to.have.length(2);
expect(ast[0].colorStops[0].type).to.equal('rgb');
expect(ast[0].colorStops[0].length.type).to.equal('%');
expect(ast[0].colorStops[0].length.value).to.equal('0');
expect(ast[0].colorStops[1].type).to.equal('rgb');
expect(ast[0].colorStops[1].length.type).to.equal('%');
expect(ast[0].colorStops[1].length.value).to.equal('100');
});
});
describe('parse gradient strings', function() {
it('should parse repeating linear gradient with bottom right direction', function() {
const gradient = 'repeating-linear-gradient(to bottom right,rgb(254, 158, 150) 0%,rgb(172, 79, 115) 100%)';
const ast = gradients.parse(gradient);
expect(ast[0].type).to.equal('repeating-linear-gradient');
expect(ast[0].orientation.type).to.equal('directional');
expect(ast[0].orientation.value).to.equal('bottom right');
expect(ast[0].colorStops).to.have.length(2);
expect(ast[0].colorStops[0].type).to.equal('rgb');
expect(ast[0].colorStops[0].value).to.eql(['254', '158', '150']);
expect(ast[0].colorStops[0].length.type).to.equal('%');
expect(ast[0].colorStops[0].length.value).to.equal('0');
expect(ast[0].colorStops[1].type).to.equal('rgb');
expect(ast[0].colorStops[1].value).to.eql(['172', '79', '115']);
expect(ast[0].colorStops[1].length.type).to.equal('%');
expect(ast[0].colorStops[1].length.value).to.equal('100');
});
describe('parse different color formats', function() {
const testGradients = [
'linear-gradient(red, blue)',
'linear-gradient(red, #00f)',
'linear-gradient(red, #0000ff)',
'linear-gradient(red, rgb(0, 0, 255))',
'linear-gradient(red, rgba(0, 0, 255, 1))',
'linear-gradient(red, hsl(240, 50%, 100%))',
'linear-gradient(red, hsla(240, 50%, 100%, 1))'
];
testGradients.forEach(function(gradient) {
it('should parse ' + gradient, function() {
const result = gradients.parse(gradient);
expect(result[0].type).to.equal('linear-gradient');
expect(result[0].colorStops).to.have.length(2);
expect(result[0].colorStops[0].type).to.equal('literal');
expect(result[0].colorStops[0].value).to.equal('red');
});
});
});
});
});

@@ -33,2 +33,7 @@ 'use strict';

it('should serialize gradient with var', function() {
var gradientDef = 'linear-gradient(var(--color-black), white)';
expect(gradients.stringify(gradients.parse(gradientDef))).to.equal(gradientDef);
});
it('should serialize gradient with rgb', function() {

@@ -35,0 +40,0 @@ var gradientDef = 'linear-gradient(rgb(1, 2, 3), white)';

{
"name": "gradient-parser",
"version": "1.0.0",
"main": "build/web.js",
"ignore": [
".editorconfig",
".gitattributes",
".gitignore",
".jshintrc",
".npmignore",
".travis.yml",
".umd",
"package.json",
"index.html",
"Makefile",
"build/node.js",
"README.md",
"*.js",
"lib/*",
"spec/*",
"LICENSE"
],
"dependencies": {
},
"devDependencies": {
}
}
'use strict';
module.exports = function (grunt) {
var config = {
app: '.',
dist: '.'
};
grunt.initConfig({
config: config,
mochaTest: {
test: {
options: {
reporter: 'spec'
},
src: ['spec/**/*.js']
}
},
concat: {
release: {
files: {
'build/node.js': ['lib/stringify.js', 'lib/parser.js', 'index.js'],
'build/web.js': ['webify.js', 'lib/parser.js', 'lib/stringify.js']
}
}
}
});
grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', [
'concat',
'mochaTest'
]);
};