Skip to content

Commit

Permalink
Add support for color literals
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Jan 30, 2025
1 parent aed7839 commit 46bf355
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 16 deletions.
5 changes: 5 additions & 0 deletions pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export {
BooleanExpressionProps,
BooleanExpressionRaws,
} from './src/expression/boolean';
export {
ColorExpression,
ColorExpressionProps,
ColorExpressionRaws,
} from './src/expression/color';
export {
NumberExpression,
NumberExpressionProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a color expression toJSON 1`] = `
{
"inputs": [
{
"css": "@#{#00f}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"raws": {},
"sassType": "color",
"source": <1:4-1:8 in 0>,
"value": {
"alpha": 1,
"channels": [
0,
0,
255,
],
"space": "rgb",
},
}
`;
249 changes: 249 additions & 0 deletions pkg/sass-parser/lib/src/expression/color.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright 2025 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import {SassColor} from 'sass';

import {ColorExpression} from '../..';
import * as utils from '../../../test/utils';

const blue = new SassColor({space: 'rgb', red: 0, green: 0, blue: 255});

describe('a color expression', () => {
let node: ColorExpression;

describe('with no alpha', () => {
function describeNode(
description: string,
create: () => ColorExpression,
): void {
describe(description, () => {
beforeEach(() => void (node = create()));

it('has sassType color', () => expect(node.sassType).toBe('color'));

it('is a color', () => expect(node.value).toEqual(blue));
});
}

describe('parsed', () => {
describeNode('hex', () => utils.parseExpression('#00f'));

describeNode('keyword', () => utils.parseExpression('blue'));
});

describeNode(
'constructed manually',
() => new ColorExpression({value: blue}),
);

describeNode('constructed from ExpressionProps', () =>
utils.fromExpressionProps({value: blue}),
);
});

describe('with no alpha', () => {
function describeNode(
description: string,
create: () => ColorExpression,
): void {
describe(description, () => {
beforeEach(() => void (node = create()));

it('has sassType color', () => expect(node.sassType).toBe('color'));

it('is a color', () =>
expect(node.value).toEqual(
new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
alpha: 0.4,
}),
));
});
}

describeNode('parsed', () => utils.parseExpression('#0a141E66'));

describeNode(
'constructed manually',
() =>
new ColorExpression({
value: new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
alpha: 0.4,
}),
}),
);

describeNode('constructed from ExpressionProps', () =>
utils.fromExpressionProps({
value: new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
alpha: 0.4,
}),
}),
);
});

describe('throws an error for non-RGB colors', () => {
beforeEach(() => void (node = utils.parseExpression('#123')));

it('in the constructor', () =>
expect(
() =>
new ColorExpression({
value: new SassColor({
space: 'hsl',
hue: 180,
saturation: 50,
lightness: 50,
}),
}),
).toThrow());

it('in the property', () =>
expect(() => {
node.value = new SassColor({
space: 'hsl',
hue: 180,
saturation: 50,
lightness: 50,
});
}).toThrow());

it('in clone', () =>
expect(() =>
node.clone({
value: new SassColor({
space: 'hsl',
hue: 180,
saturation: 50,
lightness: 50,
}),
}),
).toThrow());
});

describe('assigned new', () => {
beforeEach(() => void (node = utils.parseExpression('#123')));

it('value', () => {
node.value = new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
alpha: 0.4,
});
expect(node.value).toEqual(
new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
alpha: 0.4,
}),
);
});
});

describe('stringifies', () => {
it('without alpha', () =>
expect(utils.parseExpression('#abc').toString()).toBe('#aabbcc'));

it('with alpha', () =>
expect(utils.parseExpression('#abcd').toString()).toBe('#aabbccdd'));

describe('raws', () => {
it('with the same raw value as the expression', () =>
expect(
new ColorExpression({
value: blue,
raws: {value: {raw: 'blue', value: blue}},
}).toString(),
).toBe('blue'));

it('with a different raw value than the expression', () =>
expect(
new ColorExpression({
value: new SassColor({space: 'rgb', red: 10, green: 20, blue: 30}),
raws: {value: {raw: 'blue', value: blue}},
}).toString(),
).toBe('#0a141e'));
});
});

describe('clone', () => {
let original: ColorExpression;

beforeEach(() => {
original = utils.parseExpression('#00f');
// TODO: remove this once raws are properly parsed.
original.raws.value = {raw: 'blue', value: blue};
});

describe('with no overrides', () => {
let clone: ColorExpression;

beforeEach(() => void (clone = original.clone()));

describe('has the same properties:', () => {
it('value', () => expect(clone.value).toEqual(blue));

it('raws', () => {
expect(clone.raws.value!.raw).toBe('blue');
expect(clone.raws.value!.value).toEqual(blue);
});

it('source', () => expect(clone.source).toBe(original.source));
});

it('creates a new self', () => expect(clone).not.toBe(original));
});

describe('overrides', () => {
describe('value', () => {
it('defined', () =>
expect(
original.clone({
value: new SassColor({
space: 'rgb',
red: 10,
green: 20,
blue: 30,
}),
}).value,
).toEqual(
new SassColor({space: 'rgb', red: 10, green: 20, blue: 30}),
));

it('undefined', () =>
expect(original.clone({value: undefined}).value).toEqual(blue));
});

describe('raws', () => {
it('defined', () =>
expect(
original.clone({raws: {value: {raw: '#0000FF', value: blue}}}).raws
.value!.raw,
).toBe('#0000FF'));

it('undefined', () =>
expect(original.clone({raws: undefined}).raws.value!.raw).toBe(
'blue',
));
});
});
});

it('toJSON', () => expect(utils.parseExpression('#00f')).toMatchSnapshot());
});
Loading

0 comments on commit 46bf355

Please sign in to comment.