Skip to content

Commit

Permalink
Split file and add test
Browse files Browse the repository at this point in the history
  • Loading branch information
origami-z committed Jan 11, 2024
1 parent 1aef67e commit 05782e2
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 65 deletions.
7 changes: 5 additions & 2 deletions packages/export-variables-rest-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
"description": "Script to export Figma variables using REST API",
"main": "dist/index.js",
"scripts": {
"build": "node index.js",
"clean": "rimraf build tokens"
"dev": "node src/index.js",
"clean": "rimraf build tokens",
"test": "vitest run",
"test:watch": "vitest watch",
"test:coverage": "vitest run --coverage"
},
"repository": {
"type": "git",
Expand Down
195 changes: 195 additions & 0 deletions packages/export-variables-rest-api/src/__tests__/modifyData.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { describe, expect, test } from "vitest";
import { updateApiResponse } from "../modifyData";

describe("updateApiResponse", () => {
test("filter out remote collections and variables", () => {
const input = {
variableCollections: {
"VariableCollectionId:1": {
defaultModeId: "1:0",
id: "VariableCollectionId:1",
name: "Remote collection",
remote: true,
modes: [{ modeId: "1:0", name: "Mode" }],
key: "123456",
hiddenFromPublishing: true,
variableIds: ["VariableID:1"],
},
"VariableCollectionId:2": {
defaultModeId: "2:0",
id: "VariableCollectionId:2",
name: "Local collection",
remote: false,
modes: [{ modeId: "2:0", name: "Mode" }],
key: "223456",
hiddenFromPublishing: false,
variableIds: ["VariableID:2"],
},
},
variables: {
"VariableID:1": {
id: "VariableID:1",
name: "Color 1",
remote: true,
key: "09876",
variableCollectionId: "VariableCollectionId:1",
resolvedType: "COLOR",
description: "",
hiddenFromPublishing: true,
valuesByMode: {
"1:0": {
r: 1,
g: 1,
b: 1,
a: 1,
},
},
scopes: ["ALL_SCOPES"],
codeSyntax: {},
},
"VariableID:2": {
id: "VariableID:2",
name: "Color",
remote: false,
key: "98766",
variableCollectionId: "VariableCollectionId:2",
resolvedType: "COLOR",
description: "",
hiddenFromPublishing: false,
valuesByMode: {
"1:0": {
r: 1,
g: 1,
b: 1,
a: 1,
},
},
scopes: ["ALL_SCOPES"],
codeSyntax: {},
},
},
};
expect(
Object.values(input.variableCollections).every((c) => c.remote === false)
).toBeFalsy();
expect(
Object.values(input.variables).every((c) => c.remote === false)
).toBeFalsy();

const output = updateApiResponse(input);
expect(
Object.values(output.variableCollections).every((c) => c.remote === false)
).toBeTruthy();
expect(
Object.values(output.variables).every((c) => c.remote === false)
).toBeTruthy();
});
test("append default to variable name when used as group name, but only in the same collection", () => {
const input = {
variableCollections: {
"VariableCollectionId:1": {
defaultModeId: "1:0",
id: "VariableCollectionId:1",
name: "Remote collection",
remote: true,
modes: [{ modeId: "1:0", name: "Mode" }],
key: "123456",
hiddenFromPublishing: true,
variableIds: ["VariableID:1", "VariableID:2"],
},
"VariableCollectionId:2": {
defaultModeId: "2:0",
id: "VariableCollectionId:2",
name: "Local collection",
remote: false,
modes: [{ modeId: "2:0", name: "Mode" }],
key: "223456",
hiddenFromPublishing: false,
variableIds: ["VariableID:3"],
},
},
variables: {
"VariableID:1": {
id: "VariableID:1",
name: "Color",
remote: false,
key: "09876",
variableCollectionId: "VariableCollectionId:1",
resolvedType: "COLOR",
description: "",
hiddenFromPublishing: false,
valuesByMode: {
"1:0": {
r: 1,
g: 1,
b: 1,
a: 1,
},
},
scopes: ["ALL_SCOPES"],
codeSyntax: {},
},
"VariableID:2": {
id: "VariableID:2",
name: "Color/100",
remote: false,
key: "98766",
variableCollectionId: "VariableCollectionId:1",
resolvedType: "COLOR",
description: "",
hiddenFromPublishing: false,
valuesByMode: {
"1:0": {
r: 1,
g: 1,
b: 1,
a: 1,
},
},
scopes: ["ALL_SCOPES"],
codeSyntax: {},
},
"VariableID:3": {
id: "VariableID:3",
name: "Color",
remote: false,
key: "98766",
variableCollectionId: "VariableCollectionId:2",
resolvedType: "COLOR",
description: "",
hiddenFromPublishing: false,
valuesByMode: {
"1:0": {
r: 1,
g: 1,
b: 1,
a: 1,
},
},
scopes: ["ALL_SCOPES"],
codeSyntax: {},
},
},
};
expect(input.variables["VariableID:1"].variableCollectionId).toEqual(
input.variables["VariableID:2"].variableCollectionId
);
expect(
input.variables["VariableID:2"].name.startsWith(
input.variables["VariableID:1"].name
)
).toBeTruthy();
expect(input.variables["VariableID:3"].variableCollectionId).not.toEqual(
input.variables["VariableID:2"].variableCollectionId
);
expect(
input.variables["VariableID:2"].name.startsWith(
input.variables["VariableID:3"].name
)
).toBeTruthy();

const output = updateApiResponse(input);
expect(output.variables["VariableID:1"].name).toEqual("Color/default");
expect(output.variables["VariableID:3"].name).not.toEqual("Color/default");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { join } from "node:path";
import { ProxyAgent } from "proxy-agent";
import "dotenv/config";

import { updateApiResponse } from "./modifyData";

import StyleDictionary from "style-dictionary-utils";
import { w3cTokenJsonParser } from "style-dictionary-utils/dist/parser/w3c-token-json-parser.js";

Expand Down Expand Up @@ -96,7 +98,7 @@ function callFigmaAPI(successCallback) {
function processData(data) {
console.log("processData", data);

const newData = addDefaultToVariableNameFromApiResponse(data);
const newData = updateApiResponse(data);
console.log("data with default name suffix", newData);

const tokens = extractTokenFromVariables(newData.variables);
Expand Down Expand Up @@ -165,68 +167,6 @@ function addDefaultToNestedTokens(tokens) {
return newTokens;
}

/**
* Filter out remote variables and collections, add "/default" suffix to any
* variable (in the same collection group) which also served as group name.
*
* e.g. when "Black" and "Black/100" both exist, variable with "Black" name
* will be modified to be "Black/default".
*
* @param {*} data Figma variables REST API response's `meta` data
* @returns new data with remote filtered out and variable name modified
*/
function addDefaultToVariableNameFromApiResponse(data) {
// New data object
const newData = { variableCollections: {}, variables: {} };

// Find all non-remote collections
const nonRemoteCollections = Object.values(data.variableCollections).filter(
(c) => !c.remote
);
// Add collection to new object.variableCollections
nonRemoteCollections.forEach((c) => (newData.variableCollections[c.id] = c));

// Filter and group non-remote variables by collections
const variablesByGroup = {};
for (const v of Object.values(data.variables)) {
if (v.remote) {
continue;
}
const collectionId = v.variableCollectionId;
if (variablesByGroup[collectionId] === undefined) {
variablesByGroup[collectionId] = [];
}
variablesByGroup[collectionId].push(v);
}

// In each collection group
for (const variablesInCollection of Object.values(variablesByGroup)) {
// Sort variables by name
variablesInCollection.sort((a, b) => a.name.localeCompare(b.name));

// Iterate variables, compare name with previous name
let prevVariable = undefined;
for (let index = 0; index < variablesInCollection.length; index++) {
const element = variablesInCollection[index];
newData.variables[element.id] = element;

if (prevVariable !== undefined) {
// If name has exactly one more "/", previous one needs to be appended a "default"
const lastSlashIndex = element.name.lastIndexOf("/");
if (lastSlashIndex !== -1) {
if (element.name.substring(0, lastSlashIndex) === prevVariable.name) {
prevVariable.name = prevVariable.name + "/default";
}
}
}
prevVariable = element;
}
}

// return new object
return newData;
}

function writeTokensToFile(data, tokens) {
const allCollections = data.variableCollections;

Expand Down
61 changes: 61 additions & 0 deletions packages/export-variables-rest-api/src/modifyData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Filter out remote variables and collections, add "/default" suffix to any
* variable (in the same collection group) which also served as group name.
*
* e.g. when "Black" and "Black/100" both exist, variable with "Black" name
* will be modified to be "Black/default".
*
* @param {*} data Figma variables REST API response's `meta` data
* @returns new data with remote filtered out and variable name modified
*/
export function updateApiResponse(data) {
// New data object
const newData = { variableCollections: {}, variables: {} };

// Find all non-remote collections
const nonRemoteCollections = Object.values(data.variableCollections).filter(
(c) => !c.remote
);
// Add collection to new object.variableCollections
nonRemoteCollections.forEach((c) => (newData.variableCollections[c.id] = c));

// Filter and group non-remote variables by collections
const variablesByGroup = {};
for (const v of Object.values(data.variables)) {
if (v.remote) {
continue;
}
const collectionId = v.variableCollectionId;
if (variablesByGroup[collectionId] === undefined) {
variablesByGroup[collectionId] = [];
}
variablesByGroup[collectionId].push(v);
}

// In each collection group
for (const variablesInCollection of Object.values(variablesByGroup)) {
// Sort variables by name
variablesInCollection.sort((a, b) => a.name.localeCompare(b.name));

// Iterate variables, compare name with previous name
let prevVariable = undefined;
for (let index = 0; index < variablesInCollection.length; index++) {
const element = variablesInCollection[index];
newData.variables[element.id] = element;

if (prevVariable !== undefined) {
// If name has exactly one more "/", previous one needs to be appended a "default"
const lastSlashIndex = element.name.lastIndexOf("/");
if (lastSlashIndex !== -1) {
if (element.name.substring(0, lastSlashIndex) === prevVariable.name) {
prevVariable.name = prevVariable.name + "/default";
}
}
}
prevVariable = element;
}
}

// return new object
return newData;
}

0 comments on commit 05782e2

Please sign in to comment.