Skip to content

Commit

Permalink
Merge d5200cd into a729f1b
Browse files Browse the repository at this point in the history
  • Loading branch information
marcomontalbano committed Oct 23, 2023
2 parents a729f1b + d5200cd commit c7daaac
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 59 deletions.
6 changes: 6 additions & 0 deletions .mocharc.integration.json
@@ -0,0 +1,6 @@
{
"extension": ["js", "ts"],
"spec": "packages/**/integration.test.ts",
"require": "ts-node/register",
"file": ["test--registers.integration.ts"]
}
5 changes: 3 additions & 2 deletions .mocharc.json
Expand Up @@ -2,5 +2,6 @@
"extension": ["js", "ts"],
"spec": "packages/**/*.test.ts",
"require": "ts-node/register",
"file": ["test--registers.ts", "test--packages.ts"]
}
"file": ["test--registers.ts", "test--packages.ts"],
"ignore": ["**/integration.test.ts"]
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -20,6 +20,7 @@
"build": "tsc --build packages/**/tsconfig.json",
"lint": "yarn build && eslint . --ignore-pattern=node_modules --ignore-pattern=output --ignore-pattern=dist --ext .js,.jsx,.ts",
"test": "yarn build && TS_NODE_PROJECT='./tsconfig.mocha.json' mocha",
"test:integration": "yarn build && TS_NODE_PROJECT='./tsconfig.mocha.json' mocha --config .mocharc.integration.json",
"test:watch": "yarn test --watch --reporter=dot",
"coverage": "nyc yarn test --reporter=dot",
"coverage:watch": "npx nodemon -e js,mjs,ts --exec yarn coverage",
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/integration.test.ts
@@ -0,0 +1,42 @@
import { expect } from 'chai';

import { components as exportComponents } from './lib/export-components';
import { styles as exportStyles } from './lib/export-styles';

describe('@figma-export/core', () => {
it('Export components', async () => {
const pageNodes = await exportComponents({
fileId: 'fzYhvQpqwhZDUImRz431Qo',
token: process.env.FIGMA_TOKEN ?? '',
onlyFromPages: ['unit-test'],
// eslint-disable-next-line @typescript-eslint/no-empty-function
log: () => {},
});

// console.info(pageNodes);

expect(pageNodes.length).to.eq(1);
expect(pageNodes[0].components.length).to.eq(4);
expect(pageNodes[0].components.map((c) => c.name)).to.eql([
'figma default logo',
'figma/logo',
'figma/logo/main',
'figma/logo/main (bright)',
]);
}).timeout(60 * 1000);

it('Export styles', async () => {
const styles = await exportStyles({
fileId: 'fzYhvQpqwhZDUImRz431Qo',
token: process.env.FIGMA_TOKEN ?? '',
onlyFromPages: ['unit-test'],
// eslint-disable-next-line @typescript-eslint/no-empty-function
log: () => { },
});

// console.info(styles);

expect(styles.length).to.eq(1);
expect(styles[0].name).to.eq('purple/with/special#characters');
}).timeout(60 * 1000);
});
2 changes: 2 additions & 0 deletions packages/core/src/lib/_config.test.ts
Expand Up @@ -147,8 +147,10 @@ export const createDocument = (props: any): Figma.Document => ({

export const createPage = (children: any): Figma.Document => ({
...({} as Figma.Document),
type: 'DOCUMENT',
children: [{
...({} as Figma.Canvas),
type: 'CANVAS',
id: '10:8',
name: 'fakePage',
children,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/export-components.ts
@@ -1,6 +1,6 @@
import * as FigmaExport from '@figma-export/types';

import { getClient, getPages, enrichPagesWithSvg } from './figma';
import { getClient, getPagesWithComponents, enrichPagesWithSvg } from './figma';

export const components: FigmaExport.ComponentsCommand = async ({
token,
Expand Down Expand Up @@ -28,7 +28,7 @@ export const components: FigmaExport.ComponentsCommand = async ({
throw new Error('\'document\' is missing.');
}

const pages = getPages((document), { only: onlyFromPages, filter: filterComponent });
const pages = getPagesWithComponents(document, { only: onlyFromPages, filter: filterComponent });

log('preparing components');
const pagesWithSvg = await enrichPagesWithSvg(client, fileId, pages, {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/lib/export-styles.ts
@@ -1,6 +1,6 @@
import * as FigmaExport from '@figma-export/types';

import { getClient, getPages } from './figma';
import { getPages, getClient } from './figma';
import { fetchStyles, parseStyles } from './figmaStyles';

export const styles: FigmaExport.StylesCommand = async ({
Expand All @@ -25,7 +25,7 @@ export const styles: FigmaExport.StylesCommand = async ({
throw new Error('\'document\' is missing.');
}

const ids = getPages((document), { only: onlyFromPages })
const ids = getPages(document, onlyFromPages)
.map((page) => page.id);

log('fetching styles');
Expand Down
18 changes: 9 additions & 9 deletions packages/core/src/lib/figma.test.ts
Expand Up @@ -38,42 +38,42 @@ describe('figma.', () => {
const document = figmaDocument.createDocument({ children: [figmaDocument.page1, figmaDocument.page2] });

it('should get all pages by default', () => {
expect(figma.getPages(document))
expect(figma.getPagesWithComponents(document))
.to.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');
});

it('should get all pages if "empty" list is provided', () => {
expect(figma.getPages(document, { only: [''] }))
expect(figma.getPagesWithComponents(document, { only: [''] }))
.to.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');

expect(figma.getPages(document, { only: [] }))
expect(figma.getPagesWithComponents(document, { only: [] }))
.to.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');

expect(figma.getPages(document, { only: '' }))
expect(figma.getPagesWithComponents(document, { only: '' }))
.to.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');
});

it('should get all requested pages', () => {
expect(figma.getPages(document, { only: 'page2' }))
expect(figma.getPagesWithComponents(document, { only: 'page2' }))
.to.not.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');

expect(figma.getPages(document, { only: ['page1', 'page2'] }))
expect(figma.getPagesWithComponents(document, { only: ['page1', 'page2'] }))
.to.contain.an.item.with.property('name', 'page1')
.to.contain.an.item.with.property('name', 'page2');
});

it('should get zero results if a non existing page is provided', () => {
expect(figma.getPages(document, { only: 'page20' }))
expect(figma.getPagesWithComponents(document, { only: 'page20' }))
.to.be.an('array').that.is.empty;
});

it('should excludes pages without components', () => {
const pages = figma.getPages(
const pages = figma.getPagesWithComponents(
figmaDocument.createDocument({
children: [
figmaDocument.page1,
Expand All @@ -92,7 +92,7 @@ describe('figma.', () => {
describe('getIdsFromPages', () => {
it('should get component ids from specified pages', () => {
const document = figmaDocument.createDocument({ children: [figmaDocument.page1, figmaDocument.page2] });
const pages = figma.getPages(document, {
const pages = figma.getPagesWithComponents(document, {
only: 'page2',
});

Expand Down
44 changes: 21 additions & 23 deletions packages/core/src/lib/figma.ts
Expand Up @@ -14,7 +14,7 @@ import {
emptySvg,
} from './utils';

const getComponents = (
export const getComponents = (
children: readonly Figma.Node[] = [],
filter: FigmaExport.ComponentFilter = () => true,
pathToComponent: FigmaExport.ComponentExtras['pathToComponent'] = [],
Expand Down Expand Up @@ -51,18 +51,26 @@ const getComponents = (
return components;
};

const filterPagesByName = (pages: readonly Figma.Canvas[], pageNames: string | string[] = []): Figma.Canvas[] => {
const only = toArray(pageNames).filter((p) => p.length);
return pages.filter((page) => only.length === 0 || only.includes(page.name));
/** Check whether the given string is not empty. */
function isNotEmpty(input: string): boolean {
return input.trim() !== '';
}

export const getPages = (document: Figma.Document, pageNames: string | string[] = []): Figma.Canvas[] => {
const only = toArray(pageNames).filter(isNotEmpty);
return document.children
.filter((node): node is Figma.Canvas => {
return node.type === 'CANVAS' && (only.length === 0 || only.includes(node.name));
});
};

type GetPagesOptions = {
only?: string | string[];
filter?: FigmaExport.ComponentFilter;
}

const getPages = (document: Figma.Document, options: GetPagesOptions = {}): FigmaExport.PageNode[] => {
const pages = filterPagesByName(document.children as Figma.Canvas[], options.only);
export const getPagesWithComponents = (document: Figma.Document, options: GetPagesOptions = {}): FigmaExport.PageNode[] => {
const pages = getPages(document, options.only);

return pages
.map((page) => ({
Expand All @@ -72,20 +80,20 @@ const getPages = (document: Figma.Document, options: GetPagesOptions = {}): Figm
.filter((page) => page.components.length > 0);
};

const getIdsFromPages = (pages: FigmaExport.PageNode[]): string[] => pages.reduce((ids: string[], page) => [
export const getIdsFromPages = (pages: FigmaExport.PageNode[]): string[] => pages.reduce((ids: string[], page) => [
...ids,
...page.components.map((component) => component.id),
], []);

const getClient = (token: string): Figma.ClientInterface => {
export const getClient = (token: string): Figma.ClientInterface => {
if (!token) {
throw new Error('\'Access Token\' is missing. https://www.figma.com/developers/docs#authentication');
}

return Figma.Client({ personalAccessToken: token });
};

const fileImages = async (client: Figma.ClientInterface, fileId: string, ids: string[]): Promise<{readonly [key: string]: string}> => {
const fileImages = async (client: Figma.ClientInterface, fileId: string, ids: string[]) => {
const response = await client.fileImages(fileId, {
ids,
format: 'svg',
Expand All @@ -97,15 +105,15 @@ const fileImages = async (client: Figma.ClientInterface, fileId: string, ids: st
return response.data.images;
};

const getImages = async (client: Figma.ClientInterface, fileId: string, ids: string[]): Promise<{readonly [key: string]: string}> => {
export const getImages = async (client: Figma.ClientInterface, fileId: string, ids: string[]) => {
const idss = chunk(ids, 200);
const limit = pLimit(30);

const resolves = await Promise.all(idss.map((groupIds) => {
return limit(() => fileImages(client, fileId, groupIds));
}));

return Object.assign({}, ...resolves);
return Object.assign({}, ...resolves) as typeof resolves[number];
};

type FigmaExportFileSvg = {
Expand All @@ -119,7 +127,7 @@ type FileSvgOptions = {
onFetchCompleted?: (data: { index: number, total: number }) => void
}

const fileSvgs = async (
export const fileSvgs = async (
client: Figma.ClientInterface,
fileId: string,
ids: string[],
Expand Down Expand Up @@ -153,7 +161,7 @@ const fileSvgs = async (
return fromEntries(svgs);
};

const enrichPagesWithSvg = async (
export const enrichPagesWithSvg = async (
client: Figma.ClientInterface,
fileId: string,
pages: FigmaExport.PageNode[],
Expand All @@ -175,13 +183,3 @@ const enrichPagesWithSvg = async (
})),
}));
};

export {
getComponents,
getPages,
getIdsFromPages,
getClient,
getImages,
fileSvgs,
enrichPagesWithSvg,
};
2 changes: 1 addition & 1 deletion packages/core/src/lib/utils.ts
@@ -1,6 +1,6 @@
import axios from 'axios';

export const toArray = <T>(any: T): T[] => (Array.isArray(any) ? any : [any]);
export const toArray = <T>(any: T | T[]): T[] => (Array.isArray(any) ? any : [any]);

export const emptySvg = '<svg></svg>';

Expand Down
16 changes: 8 additions & 8 deletions packages/output-components-as-es6/src/index.test.ts
Expand Up @@ -70,7 +70,7 @@ describe('outputter as es6', () => {
it('should export all components into an es6 file', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages);

nockScope.done();
Expand All @@ -91,7 +91,7 @@ describe('outputter as es6', () => {
it('should use "variablePrefix" and "variableSuffix" options to prepend or append a text to the variable name', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages);

nockScope.done();
Expand All @@ -113,7 +113,7 @@ describe('outputter as es6', () => {
it('should export all components into an es6 file using base64 encoding if set', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages);

nockScope.done();
Expand All @@ -134,7 +134,7 @@ describe('outputter as es6', () => {
it('should export all components into an es6 file using dataUrl if set', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages);

nockScope.done();
Expand All @@ -161,7 +161,7 @@ describe('outputter as es6', () => {
it('should not break when transformers return "undefined"', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages, {
transformers: [async () => undefined],
});
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('outputter as es6', () => {
sinon.stub(fs, 'writeFileSync');

const document = figmaDocument.createDocument({ children: [page] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const spyOutputter = sinon.spy(outputter);

return spyOutputter({
Expand All @@ -212,7 +212,7 @@ describe('outputter as es6', () => {
sinon.stub(fs, 'writeFileSync');

const document = figmaDocument.createDocument({ children: [page] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const spyOutputter = sinon.spy(outputter);

return spyOutputter({
Expand All @@ -228,7 +228,7 @@ describe('outputter as es6', () => {
it('should create folders and subfolders when pageName contains slashes', async () => {
const writeFileSync = sinon.stub(fs, 'writeFileSync');
const document = figmaDocument.createDocument({ children: [figmaDocument.page1WithSlashes] });
const pages: FigmaExport.PageNode[] = figma.getPages(document);
const pages: FigmaExport.PageNode[] = figma.getPagesWithComponents(document);
const pagesWithSvg = await figma.enrichPagesWithSvg(client, 'fileABCD', pages);

nockScope.done();
Expand Down
2 changes: 1 addition & 1 deletion packages/output-components-as-stdout/src/index.test.ts
Expand Up @@ -10,7 +10,7 @@ import outputter = require('./index');
describe('outputter as stdout', () => {
it('should output pages in console as json', async () => {
const document = figmaDocument.createDocument({ children: [figmaDocument.page1] });
const pages = figma.getPages(document);
const pages = figma.getPagesWithComponents(document);
await outputter()(pages);
expect(console.log).to.have.been.calledOnce;
expect(console.log).to.have.been.calledWith(`${JSON.stringify(pages)}`);
Expand Down

0 comments on commit c7daaac

Please sign in to comment.