Skip to content

Commit

Permalink
feat(references): ability to reference other tokens without 'value' (#…
Browse files Browse the repository at this point in the history
…746)

* feat(references): ability to reference other tokens without 'value'

* chore(tests): add more tests for dot value
  • Loading branch information
dbanksdesign authored Dec 15, 2021
1 parent 3565af1 commit c6f482e
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 17 deletions.
2 changes: 1 addition & 1 deletion __integration__/android.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('android', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
android: {
transformGroup: `android`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/compose.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('compose', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
compose: {
transformGroup: `compose`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/css.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('css', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
// Testing proper string interpolation with multiple references here.
// This is a CSS/web-specific thing so only including them in this
// integration test.
Expand Down
2 changes: 1 addition & 1 deletion __integration__/flutter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('flutter', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `flutter`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/iOSObjectiveC.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('ios objective-c', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `ios`,
Expand Down
12 changes: 6 additions & 6 deletions __integration__/logging/file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe(`integration`, () => {
describe(`file`, () => {
it(`should warn user empty properties`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -58,7 +58,7 @@ describe(`integration`, () => {
it(`should not warn user of empty properties with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -75,7 +75,7 @@ describe(`integration`, () => {

it(`should warn user of name collisions`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
// no name transform means there will be name collisions
Expand All @@ -95,7 +95,7 @@ describe(`integration`, () => {
it(`should not warn user of name collisions with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
// no name transform means there will be name collisions
Expand All @@ -114,7 +114,7 @@ describe(`integration`, () => {

it(`should warn user of filtered references`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand All @@ -138,7 +138,7 @@ describe(`integration`, () => {
it(`should not warn user of filtered references with log level set to error`, () => {
StyleDictionary.extend({
logLevel: `error`,
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `css`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/outputReferences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('integration', () => {
StyleDictionary.extend({
// we are only testing showFileHeader options so we don't need
// the full source.
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: 'css',
Expand Down
2 changes: 1 addition & 1 deletion __integration__/scss.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const {buildPath} = require('./_constants');
describe(`integration`, () => {
describe(`scss`, () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
css: {
transformGroup: `scss`,
Expand Down
2 changes: 1 addition & 1 deletion __integration__/swift.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {buildPath} = require('./_constants');
describe('integration', () => {
describe('swift', () => {
StyleDictionary.extend({
source: [`__integration__/tokens/**/*.json`],
source: [`__integration__/tokens/**/*.json?(c)`],
platforms: {
flutter: {
transformGroup: `ios-swift`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
"primary": { "value": "{color.core.neutral.1100.value}" },
"secondary": { "value": "{color.core.neutral.900.value}" },
"tertiary": { "value": "{color.core.neutral.800.value}" },

"interactive": {
"_": { "value": "{color.brand.primary.value}" },
"hover": { "value": "{color.brand.primary.value}" },
"active": { "value": "{color.brand.secondary.value}" },
"disabled": { "value": "{color.font.tertiary.value}" }
},

"danger": { "value": "{color.core.red.1000.value}" },
"warning": { "value": "{color.core.orange.1000.value}" },
"success": { "value": "{color.core.green.1000.value}" }
// make sure references without .value work too
"success": { "value": "{color.core.green.1000}" }
}
}
}
175 changes: 175 additions & 0 deletions __tests__/exportPlatform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,179 @@ describe('exportPlatform', () => {
});
});

it('should handle .value and non .value references per the W3C spec', () => {
const tokens = {
colors: {
red: { value: '#f00' },
error: { value: '{colors.red}' },
danger: { value: '{colors.error}' },
alert: { value: '{colors.error.value}' },
}
}

const expected = {
colors: {
red: {
value: '#f00',
name: 'colors-red',
path: ['colors','red'],
attributes: {
category: 'colors',
type: 'red'
},
original: {
value: '#f00'
}
},
error: {
value: '#f00',
name: 'colors-error',
path: ['colors','error'],
attributes: {
category: 'colors',
type: 'error'
},
original: {
value: '{colors.red}'
}
},
danger: {
value: '#f00',
name: 'colors-danger',
path: ['colors','danger'],
attributes: {
category: 'colors',
type: 'danger'
},
original: {
value: '{colors.error}'
}
},
alert: {
value: '#f00',
name: 'colors-alert',
path: ['colors','alert'],
attributes: {
category: 'colors',
type: 'alert'
},
original: {
value: '{colors.error.value}'
}
},
}
}

const actual = StyleDictionary.extend({
tokens,
platforms: {
css: {
transformGroup: `css`
}
}
}).exportPlatform('css');
expect(actual).toEqual(expected);
});

describe('token references without .value', () => {
const tokens = {
color: {
red: { value: '#f00' },
error: { value: '{color.red}' },
errorWithValue: { value: '{color.red.value}' },
danger: { value: '{color.error}' },
dangerWithValue: { value: '{color.error.value}' },
dangerErrorValue: { value: '{color.errorWithValue}' }
}
}

const actual = StyleDictionary.extend({
tokens,
platforms: {
css: {
transformGroup: 'css'
}
}
}).exportPlatform('css');

it('should work if referenced directly', () => {
expect(actual.color.error.value).toEqual('#ff0000');
});
it('should work if there is a transitive reference', () => {
expect(actual.color.danger.value).toEqual('#ff0000');
});
it('should work if there is a transitive reference with .value', () => {
expect(actual.color.errorWithValue.value).toEqual('#ff0000');
expect(actual.color.dangerWithValue.value).toEqual('#ff0000');
expect(actual.color.dangerErrorValue.value).toEqual('#ff0000');
});
});

describe('non-token references', () => {
const tokens = {
nonTokenColor: 'hsl(10,20%,20%)',
hue: {
red: '10',
green: '120',
blue: '220'
},
comment: 'hello',
color: {
red: {
// Note having references as part of the value,
// either in an object like this, or in an interpolated
// string like below, requires the use of transitive
// transforms if you want it to be transformed.
value: {
h: '{hue.red}',
s: '100%',
l: '50%'
}
},
blue: {
value: '{nonTokenColor}',
comment: '{comment}'
},
green: {
value: 'hsl({hue.green}, 50%, 50%)'
}
}
}

// making the css/color transform transitive so we can be sure the references
// get resolved properly and transformed.
const transitiveTransform = Object.assign({},
StyleDictionary.transform['color/css'],
{transitive: true}
);

const actual = StyleDictionary.extend({
tokens,
transform: {
transitiveTransform
},
platforms: {
css: {
transforms: [
'attribute/cti',
'name/cti/kebab',
'transitiveTransform'
]
}
}
}).exportPlatform('css');

it('should work if referenced directly', () => {
expect(actual.color.blue.value).toEqual('#3d2c29');
});
it('should work if referenced from a non-value', () => {
expect(actual.color.blue.comment).toEqual(tokens.comment);
});
it('should work if interpolated', () => {
expect(actual.color.green.value).toEqual('#40bf40');
});
it('should work if part of an object value', () => {
expect(actual.color.red.value).toEqual('#ff2b00');
});
});
});
11 changes: 11 additions & 0 deletions lib/utils/resolveObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,19 @@ function compile_value(value, stack) {
// Find what the value is referencing
const pathName = getPath(variable, options);
const context = getName(current_context, options);
const refHasValue = pathName[pathName.length-1] === 'value';
ref = resolveReference(pathName, updated_object);

// If the reference doesn't end in 'value'
// and
// the reference points to someplace that has a `value` attribute
// we should take the '.value' of the reference
// per the W3C draft spec where references do not have .value
// https://design-tokens.github.io/community-group/format/#aliases-references
if (!refHasValue && ref && ref.hasOwnProperty('value')) {
ref = ref.value;
}

if (typeof ref !== 'undefined') {
if (typeof ref === 'string') {
to_ret = value.replace(match, ref);
Expand Down

0 comments on commit c6f482e

Please sign in to comment.