Skip to content

Commit

Permalink
fix: allow outputReferences to work on non-string values
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema committed Oct 12, 2023
1 parent 5e00a6c commit 8021ec0
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 40 deletions.
167 changes: 155 additions & 12 deletions __tests__/common/formatHelpers/createPropertyFormatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ const dictionary = createDictionary({
value: '5px',
type: 'spacing'
},
bar: {
ref: {
original: {
value: '{tokens.foo}',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'bar'
type: 'ref'
},
name: 'tokens-bar',
path: ['tokens', 'bar'],
name: 'tokens-ref',
path: ['tokens', 'ref'],
value: '5px',
type: 'spacing'
},
}
}
}
});
Expand All @@ -65,25 +65,146 @@ const transformedDictionary = createDictionary({
value: '5px',
type: 'spacing'
},
bar: {
ref: {
original: {
value: '{tokens.foo}',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'bar'
type: 'ref'
},
name: 'tokens-bar',
path: ['tokens', 'bar'],
name: 'tokens-ref',
path: ['tokens', 'ref'],
value: 'changed by transitive transform',
type: 'spacing'
},
}
}
});

const numberDictionary = createDictionary({
properties: {
tokens: {
foo: {
original: {
value: 10,
type: 'dimension'
},
attributes: {
category: 'tokens',
type: 'foo'
},
name: 'tokens-foo',
path: ['tokens', 'foo'],
value: 10,
type: 'dimension'
},
ref: {
original: {
value: '{tokens.foo}',
type: 'dimension'
},
attributes: {
category: 'tokens',
type: 'ref'
},
name: 'tokens-ref',
path: ['tokens', 'ref'],
value: 10,
type: 'dimension'
},
}
}
})

const multiDictionary = createDictionary({
properties: {
tokens: {
foo: {
original: {
value: '10px',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'foo'
},
name: 'tokens-foo',
path: ['tokens', 'foo'],
value: '10px',
type: 'spacing'
},
bar: {
original: {
value: '15px',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'bar'
},
name: 'tokens-bar',
path: ['tokens', 'bar'],
value: '15px',
type: 'spacing'
},
ref: {
original: {
value: '{tokens.foo} 5px {tokens.bar}',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'ref'
},
name: 'tokens-ref',
path: ['tokens', 'ref'],
value: '10px 5px 15px',
type: 'spacing'
},
}
}
})

const objectDictionary = createDictionary({
properties: {
tokens: {
foo: {
original: {
value: '5px',
type: 'spacing'
},
attributes: {
category: 'tokens',
type: 'foo'
},
name: 'tokens-foo',
path: ['tokens', 'foo'],
value: '5px',
type: 'spacing'
},
ref: {
original: {
value: {
width: '{tokens.foo}',
style: 'dashed',
color: '#FF00FF'
},
type: 'border'
},
attributes: {
category: 'tokens',
type: 'ref'
},
name: 'tokens-ref',
path: ['tokens', 'ref'],
value: '5px dashed #FF00FF',
type: 'border'
}
}
}
});


describe('common', () => {
Expand All @@ -92,13 +213,35 @@ describe('common', () => {
it('should support outputReferences', () => {
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary, format: 'css' })
expect(propFormatter(dictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
expect(propFormatter(dictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: var(--tokens-foo);');
expect(propFormatter(dictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);');
})

it('should support outputReferences when values are transformed by (transitive) "value" transforms', () => {
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary, format: 'css' })
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: transformedDictionary, format: 'css' })
expect(propFormatter(transformedDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
expect(propFormatter(transformedDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: var(--tokens-foo);');
expect(propFormatter(transformedDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);');
})

it('should support number values for outputReferences', () => {
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: numberDictionary, format: 'css' })
expect(propFormatter(numberDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10;');
expect(propFormatter(numberDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo);');
})

it('should support multiple references for outputReferences', () => {
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: multiDictionary, format: 'css' })
expect(propFormatter(multiDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 10px;');
expect(propFormatter(multiDictionary.tokens.tokens.bar)).toEqual(' --tokens-bar: 15px;');
expect(propFormatter(multiDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) 5px var(--tokens-bar);');
})

it('should support object value references for outputReferences', () => {
// The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform)
// to change it from an object to a string. In our example, we use a border CSS shorthand for border token.
// In this case, since it is an object value, we will run the transformation on the transformed (string) value.
const propFormatter = createPropertyFormatter({ outputReferences: true, dictionary: objectDictionary, format: 'css' })
expect(propFormatter(objectDictionary.tokens.tokens.foo)).toEqual(' --tokens-foo: 5px;');
expect(propFormatter(objectDictionary.tokens.tokens.ref)).toEqual(' --tokens-ref: var(--tokens-foo) dashed #FF00FF;');
})
})
})
Expand Down
57 changes: 29 additions & 28 deletions lib/common/formatHelpers/createPropertyFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,41 +104,42 @@ function createPropertyFormatter({
if (outputReferences && dictionary.usesReference(prop.original.value)) {
// Formats that use this function expect `value` to be a string
// or else you will get '[object Object]' in the output
if (typeof value === 'string') {
const refs = dictionary.getReferences(prop.original.value);
const refs = dictionary.getReferences(prop.original.value);

// original can either be string value or an object value
const originalIsString = typeof prop.original.value === 'string';
// original can either be an object value, which requires transitive value transformation in web CSS formats
// or a different (primitive) type, meaning it can be stringified.
const originalIsObject = typeof prop.original.value === 'object' && prop.original.value !== null;

// Set the value to the original value with refs first, undoing value-changing transitive transforms
if (originalIsString) {
value = prop.original.value;
}
if (!originalIsObject) {
// when original is object value, we replace value by matching ref.value and putting a var instead.
// Due to the original.value being an object, it requires transformation, so undoing the transformation
// by replacing value with original.value is not possible.

// when original is string value, we replace value by matching original.value and putting a var instead
// this is more friendly to transitive transforms that transform the string values
value = prop.original.value;
}

refs.forEach(ref => {
// value should be a string that contains the resolved reference
// because Style Dictionary resolved this in the resolution step.
// Here we are undoing that by replacing the value with
// the reference's name
if (ref.value && ref.name) {
const replaceFunc = function() {
if (format === 'css') {
if (outputReferenceFallbacks) {
return `var(${prefix}${ref.name}, ${ref.value})`;
} else {
return `var(${prefix}${ref.name})`;
}
refs.forEach(ref => {
// value should be a string that contains the resolved reference
// because Style Dictionary resolved this in the resolution step.
// Here we are undoing that by replacing the value with
// the reference's name
if (ref.value && ref.name) {
const replaceFunc = function() {
if (format === 'css') {
if (outputReferenceFallbacks) {
return `var(${prefix}${ref.name}, ${ref.value})`;
} else {
return `${prefix}${ref.name}`;
return `var(${prefix}${ref.name})`;
}
} else {
return `${prefix}${ref.name}`;
}
// when original is object value, we replace value by matching ref.value and putting a var instead
// when original is string value, we replace value by matching original.value and putting a var instead
// this is more friendly to transitive transforms that transform the string values
value = value.replace(originalIsString ? new RegExp(`{${ref.path.join('.')}(.value)?}`, 'g') : ref.value, replaceFunc);
}
});
}
value = value.replace(originalIsObject ? ref.value : new RegExp(`{${ref.path.join('.')}(.value)?}`, 'g'), replaceFunc);
}
});
}

to_ret_prop += prop.attributes.category === 'asset' ? `"${value}"` : value;
Expand Down

0 comments on commit 8021ec0

Please sign in to comment.