├── .eslintignore ├── .gitignore ├── commitlint.config.js ├── bin └── snap-it.js ├── .prettierrc ├── src ├── types │ ├── getNumberType.ts │ ├── getObjectType.ts │ ├── getBooleanType.ts │ └── getStringType.ts ├── utils │ ├── functionUtils.ts │ └── logger.ts ├── serializeSymbol.ts ├── getProps.ts ├── generateTestFile.ts └── index.ts ├── .babelrc ├── lib ├── types │ ├── getNumberType.js.map │ ├── getObjectType.js.map │ ├── getBooleanType.js.map │ ├── getObjectType.js │ ├── getNumberType.js │ ├── getBooleanType.js │ ├── getStringType.js │ └── getStringType.js.map ├── utils │ ├── functionUtils.js │ ├── functionUtils.js.map │ ├── logger.js │ └── logger.js.map ├── serializeSymbol.js.map ├── serializeSymbol.js ├── generateTestFile.js ├── getProps.js ├── getProps.js.map ├── index.js ├── generateTestFile.js.map └── index.js.map ├── .release-it.json ├── example ├── Search.tsx └── Search.test.tsx ├── tsconfig.json ├── template └── template ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | template/* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | __tests__ 3 | yarn-error.log 4 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /bin/snap-it.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line import/no-commonjs 4 | require('../lib/index'); 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | 'singleQuote': true, 3 | 'tabWidth': 2, 4 | 'trailingComma': 'es5', 5 | 'useTabs': false 6 | } 7 | -------------------------------------------------------------------------------- /src/types/getNumberType.ts: -------------------------------------------------------------------------------- 1 | function getNumberType(): string { 2 | return '123'; 3 | } 4 | 5 | export default getNumberType; 6 | -------------------------------------------------------------------------------- /src/types/getObjectType.ts: -------------------------------------------------------------------------------- 1 | function getObjectType(): string { 2 | return '{}'; 3 | } 4 | 5 | export default getObjectType; 6 | -------------------------------------------------------------------------------- /src/types/getBooleanType.ts: -------------------------------------------------------------------------------- 1 | function getBooleanType(): string { 2 | return 'true'; 3 | } 4 | 5 | export default getBooleanType; 6 | -------------------------------------------------------------------------------- /src/utils/functionUtils.ts: -------------------------------------------------------------------------------- 1 | export function isFunction(type: string) { 2 | const brackets = /\((.*)\)/; 3 | return !!brackets.test(type); 4 | } 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "node": "8" 6 | } 7 | }], 8 | "@babel/preset-typescript" 9 | ], 10 | "plugins": ["@babel/plugin-proposal-optional-chaining"] 11 | } 12 | -------------------------------------------------------------------------------- /lib/types/getNumberType.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/types/getNumberType.ts"],"names":["getNumberType"],"mappings":";;;;;;;AAAA,SAASA,aAAT,GAAiC;AAC/B,SAAO,KAAP;AACD;;eAEcA,a","sourcesContent":["function getNumberType(): string {\n return '123';\n}\n\nexport default getNumberType;\n"],"file":"getNumberType.js"} -------------------------------------------------------------------------------- /lib/types/getObjectType.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/types/getObjectType.ts"],"names":["getObjectType"],"mappings":";;;;;;;AAAA,SAASA,aAAT,GAAiC;AAC/B,SAAO,IAAP;AACD;;eAEcA,a","sourcesContent":["function getObjectType(): string {\n return '{}';\n}\n\nexport default getObjectType;\n"],"file":"getObjectType.js"} -------------------------------------------------------------------------------- /lib/types/getBooleanType.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/types/getBooleanType.ts"],"names":["getBooleanType"],"mappings":";;;;;;;AAAA,SAASA,cAAT,GAAkC;AAChC,SAAO,MAAP;AACD;;eAEcA,c","sourcesContent":["function getBooleanType(): string {\n return 'true';\n}\n\nexport default getBooleanType;\n"],"file":"getBooleanType.js"} -------------------------------------------------------------------------------- /lib/utils/functionUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.isFunction = isFunction; 7 | 8 | function isFunction(type) { 9 | const brackets = /\((.*)\)/; 10 | return !!brackets.test(type); 11 | } 12 | //# sourceMappingURL=functionUtils.js.map -------------------------------------------------------------------------------- /lib/types/getObjectType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | function getObjectType() { 9 | return '{}'; 10 | } 11 | 12 | var _default = getObjectType; 13 | exports.default = _default; 14 | //# sourceMappingURL=getObjectType.js.map -------------------------------------------------------------------------------- /lib/types/getNumberType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | function getNumberType() { 9 | return '123'; 10 | } 11 | 12 | var _default = getNumberType; 13 | exports.default = _default; 14 | //# sourceMappingURL=getNumberType.js.map -------------------------------------------------------------------------------- /lib/types/getBooleanType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | function getBooleanType() { 9 | return 'true'; 10 | } 11 | 12 | var _default = getBooleanType; 13 | exports.default = _default; 14 | //# sourceMappingURL=getBooleanType.js.map -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore: release ${version}", 4 | "tagName": "v${version}" 5 | }, 6 | "npm": { 7 | "publish": true 8 | }, 9 | "github": { 10 | "release": true 11 | }, 12 | "plugins": { 13 | "@release-it/conventional-changelog": { 14 | "preset": "angular" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/types/getStringType.ts: -------------------------------------------------------------------------------- 1 | function getStringType(name: string): string { 2 | // TODO: move checking color to separate function and make it maybe regex? 3 | switch (name) { 4 | case 'color': 5 | case 'backgroundColor': 6 | case 'bgColor': 7 | return '#ffffff'; 8 | default: 9 | return 'testing string'; 10 | } 11 | } 12 | 13 | export default getStringType; 14 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | const logger = (type: string, color: Function) => (...messages: unknown[]) => { 4 | console.log(chalk.bgGray(chalk.white(type)), color(...messages)); 5 | }; 6 | 7 | export const info = logger('ℹ', chalk.white); 8 | export const warn = logger('⚠', chalk.yellow); 9 | export const error = logger('✖', chalk.red); 10 | export const success = logger('✓', chalk.green); 11 | -------------------------------------------------------------------------------- /lib/utils/functionUtils.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/utils/functionUtils.ts"],"names":["isFunction","type","brackets","test"],"mappings":";;;;;;;AAAO,SAASA,UAAT,CAAoBC,IAApB,EAAkC;AACvC,QAAMC,QAAQ,GAAG,UAAjB;AACA,SAAO,CAAC,CAACA,QAAQ,CAACC,IAAT,CAAcF,IAAd,CAAT;AACD","sourcesContent":["export function isFunction(type: string) {\n const brackets = /\\((.*)\\)/;\n return !!brackets.test(type);\n}\n"],"file":"functionUtils.js"} -------------------------------------------------------------------------------- /example/Search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'react-native'; 3 | 4 | interface Props { 5 | numberTest?: number; 6 | booleanTest?: boolean; 7 | stringTest?: string; 8 | anyTest?: any; 9 | functionTest?: () => void; 10 | color?: string; 11 | backgroundColor?: string; 12 | bgColor?: string; 13 | 14 | requiredTest: string; 15 | } 16 | 17 | export default function Search({}: Props) { 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /lib/types/getStringType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | function getStringType(name) { 9 | // TODO: move checking color to separate function and make it maybe regex? 10 | switch (name) { 11 | case 'color': 12 | case 'backgroundColor': 13 | case 'bgColor': 14 | return '#ffffff'; 15 | 16 | default: 17 | return 'testing string'; 18 | } 19 | } 20 | 21 | var _default = getStringType; 22 | exports.default = _default; 23 | //# sourceMappingURL=getStringType.js.map -------------------------------------------------------------------------------- /lib/types/getStringType.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/types/getStringType.ts"],"names":["getStringType","name"],"mappings":";;;;;;;AAAA,SAASA,aAAT,CAAuBC,IAAvB,EAA6C;AAC3C;AACA,UAAQA,IAAR;AACE,SAAK,OAAL;AACA,SAAK,iBAAL;AACA,SAAK,SAAL;AACE,aAAO,SAAP;;AACF;AACE,aAAO,gBAAP;AANJ;AAQD;;eAEcD,a","sourcesContent":["function getStringType(name: string): string {\n // TODO: move checking color to separate function and make it maybe regex?\n switch (name) {\n case 'color':\n case 'backgroundColor':\n case 'bgColor':\n return '#ffffff';\n default:\n return 'testing string';\n }\n}\n\nexport default getStringType;\n"],"file":"getStringType.js"} -------------------------------------------------------------------------------- /src/serializeSymbol.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | export interface TestPropsInfo { 4 | name: string; 5 | documentation: string; 6 | type: string; 7 | required: boolean; 8 | } 9 | 10 | export function serializeSymbol( 11 | symbol: ts.Symbol, 12 | checker: ts.TypeChecker 13 | ): TestPropsInfo { 14 | return { 15 | name: symbol.getName(), 16 | documentation: ts.displayPartsToString( 17 | symbol.getDocumentationComment(checker) 18 | ), 19 | type: checker.typeToString( 20 | checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!) 21 | ), 22 | // @ts-ignore 23 | required: !symbol.valueDeclaration.questionToken, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "jsx": "react", 8 | "lib": ["esnext"], 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "resolveJsonModule": true, 17 | "skipLibCheck": true, 18 | "target": "esnext", 19 | "plugins": [{ "name": "typescript-tslint-plugin" }], 20 | }, 21 | "exclude": ["template/**/*"] 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.success = exports.error = exports.warn = exports.info = void 0; 7 | 8 | var _chalk = _interopRequireDefault(require("chalk")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 11 | 12 | const logger = (type, color) => (...messages) => { 13 | console.log(_chalk.default.bgGray(_chalk.default.white(type)), color(...messages)); 14 | }; 15 | 16 | const info = logger('ℹ', _chalk.default.white); 17 | exports.info = info; 18 | const warn = logger('⚠', _chalk.default.yellow); 19 | exports.warn = warn; 20 | const error = logger('✖', _chalk.default.red); 21 | exports.error = error; 22 | const success = logger('✓', _chalk.default.green); 23 | exports.success = success; 24 | //# sourceMappingURL=logger.js.map -------------------------------------------------------------------------------- /template/template: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react-native'; 3 | 4 | import { <%= componentName %> } from '<%= filepath %>'; 5 | 6 | describe('<%= componentName %>', () => { 7 | test('Snaphot for required props', () => { 8 | const props = { 9 | <%- getRequiredProps(data) %> 10 | } 11 | const tree = render(<<%= componentName %> {...props} />).toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | <% for(var i=0; i < getOptionalPropsArray(data).length; i++) {%> 15 | test('Snaphot for <%= getOptionalPropsArray(data)[i].name %>', () => { 16 | const props = { 17 | <%- getRequiredProps(data) %> 18 | <%= getOptionalPropsArray(data)[i].name %>: <%- getPropValue(getOptionalPropsArray(data)[i]) %>, 19 | } 20 | const tree = render(<<%= componentName %> {...props} />).toJSON(); 21 | expect(tree).toMatchSnapshot(); 22 | }); 23 | <% } %> 24 | }); 25 | -------------------------------------------------------------------------------- /lib/utils/logger.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/utils/logger.ts"],"names":["logger","type","color","messages","console","log","chalk","bgGray","white","info","warn","yellow","error","red","success","green"],"mappings":";;;;;;;AAAA;;;;AAEA,MAAMA,MAAM,GAAG,CAACC,IAAD,EAAeC,KAAf,KAAmC,CAAC,GAAGC,QAAJ,KAA4B;AAC5EC,EAAAA,OAAO,CAACC,GAAR,CAAYC,eAAMC,MAAN,CAAaD,eAAME,KAAN,CAAYP,IAAZ,CAAb,CAAZ,EAA6CC,KAAK,CAAC,GAAGC,QAAJ,CAAlD;AACD,CAFD;;AAIO,MAAMM,IAAI,GAAGT,MAAM,CAAC,GAAD,EAAMM,eAAME,KAAZ,CAAnB;;AACA,MAAME,IAAI,GAAGV,MAAM,CAAC,GAAD,EAAMM,eAAMK,MAAZ,CAAnB;;AACA,MAAMC,KAAK,GAAGZ,MAAM,CAAC,GAAD,EAAMM,eAAMO,GAAZ,CAApB;;AACA,MAAMC,OAAO,GAAGd,MAAM,CAAC,GAAD,EAAMM,eAAMS,KAAZ,CAAtB","sourcesContent":["import chalk from 'chalk';\n\nconst logger = (type: string, color: Function) => (...messages: unknown[]) => {\n console.log(chalk.bgGray(chalk.white(type)), color(...messages));\n};\n\nexport const info = logger('ℹ', chalk.white);\nexport const warn = logger('⚠', chalk.yellow);\nexport const error = logger('✖', chalk.red);\nexport const success = logger('✓', chalk.green);\n"],"file":"logger.js"} -------------------------------------------------------------------------------- /lib/serializeSymbol.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/serializeSymbol.ts"],"names":["serializeSymbol","symbol","checker","name","getName","documentation","ts","displayPartsToString","getDocumentationComment","type","typeToString","getTypeOfSymbolAtLocation","valueDeclaration","required","questionToken"],"mappings":";;;;;;;AAAA;;;;;;AASO,SAASA,eAAT,CACLC,MADK,EAELC,OAFK,EAGU;AACf,SAAO;AACLC,IAAAA,IAAI,EAAEF,MAAM,CAACG,OAAP,EADD;AAELC,IAAAA,aAAa,EAAEC,EAAE,CAACC,oBAAH,CACbN,MAAM,CAACO,uBAAP,CAA+BN,OAA/B,CADa,CAFV;AAKLO,IAAAA,IAAI,EAAEP,OAAO,CAACQ,YAAR,CACJR,OAAO,CAACS,yBAAR,CAAkCV,MAAlC,EAA0CA,MAAM,CAACW,gBAAjD,CADI,CALD;AAQL;AACAC,IAAAA,QAAQ,EAAE,CAACZ,MAAM,CAACW,gBAAP,CAAwBE;AAT9B,GAAP;AAWD","sourcesContent":["import * as ts from 'typescript';\n\nexport interface TestPropsInfo {\n name: string;\n documentation: string;\n type: string;\n required: boolean;\n}\n\nexport function serializeSymbol(\n symbol: ts.Symbol,\n checker: ts.TypeChecker\n): TestPropsInfo {\n return {\n name: symbol.getName(),\n documentation: ts.displayPartsToString(\n symbol.getDocumentationComment(checker)\n ),\n type: checker.typeToString(\n checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!)\n ),\n // @ts-ignore\n required: !symbol.valueDeclaration.questionToken,\n };\n}\n"],"file":"serializeSymbol.js"} -------------------------------------------------------------------------------- /src/getProps.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './utils/functionUtils'; 2 | import getStringType from './types/getStringType'; 3 | import getNumberType from './types/getNumberType'; 4 | import getBooleanType from './types/getBooleanType'; 5 | import getObjectType from './types/getObjectType'; 6 | 7 | export function getRequiredProps(data) { 8 | let requriedString = ''; 9 | for (let i = 0; i < data.length; i++) { 10 | if (data[i].required) { 11 | requriedString += `${data[i].name}: ${getPropValue(data[i])},\n`; 12 | } 13 | } 14 | return requriedString; 15 | } 16 | 17 | // TODO: move generating this data to be similar to getRequiredProps so we can get rid of implementation inside template (template/template) 18 | export function getOptionalPropsArray(data) { 19 | return data.filter((d) => !d.required); 20 | } 21 | 22 | export function getPropValue(data) { 23 | const type = data.type; 24 | 25 | if (isFunction(type)) { 26 | return 'jest.fn()'; 27 | } 28 | // TODO: move all of this to separate functions like getStringType 29 | switch (type) { 30 | case 'string': 31 | return `'${getStringType(data.name)}'`; 32 | case 'number': 33 | return `'${getNumberType()}'`; 34 | case 'boolean': 35 | return `'${getBooleanType()}'`; 36 | case 'object': 37 | return `'${getObjectType()}'`; 38 | case 'any': 39 | return `'${getObjectType()}'`; 40 | default: 41 | return 'undefined'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/generateTestFile.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import ejs from 'ejs'; 3 | import fs from 'fs-extra'; 4 | 5 | import { info, success } from './utils/logger'; 6 | import { TestPropsInfo } from './serializeSymbol'; 7 | import { 8 | getRequiredProps, 9 | getOptionalPropsArray, 10 | getPropValue, 11 | } from './getProps'; 12 | 13 | const TEMPLATE = path.resolve(__dirname, '../template/template'); 14 | 15 | export async function generateTestFile( 16 | data: TestPropsInfo[], 17 | filename: string, 18 | filepath: string, 19 | options: { saveToSameFolder: boolean } 20 | ) { 21 | const root = process.cwd(); 22 | 23 | let dest = path.relative(root, '__tests__'); 24 | if (options.saveToSameFolder) { 25 | console.log(filepath); 26 | dest = path.relative(root, filepath.match(/(.*)[\/\\]/)[1] || ''); 27 | } 28 | const filepathRelative = options.saveToSameFolder 29 | ? `./${path.relative(dest, filepath)}` 30 | : path.relative(dest, filepath); 31 | await fs.mkdirp(dest); 32 | 33 | const target = `${dest}/${filename}.test.tsx`; 34 | const content = await fs.readFile(TEMPLATE, 'utf8'); 35 | 36 | info(`Writing to ${target}`); 37 | await fs.writeFile( 38 | target, 39 | ejs.render(content, { 40 | data, 41 | getRequiredProps, 42 | getOptionalPropsArray, 43 | getPropValue, 44 | componentName: filename, 45 | filepath: filepathRelative, 46 | }) 47 | ); 48 | success(`Done, check your test in ${target}`); 49 | } 50 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import * as ts from 'typescript'; 3 | import yargs from 'yargs'; 4 | 5 | import { info, error } from './utils/logger'; 6 | import { serializeSymbol, TestPropsInfo } from './serializeSymbol'; 7 | import { generateTestFile } from './generateTestFile'; 8 | 9 | yargs.command('g ', 'create a snapshot for component', {}, snapshot).argv; 10 | 11 | async function snapshot(argv: yargs.Arguments) { 12 | const saveToSameFolder = argv.direct === 'true'; 13 | 14 | const root = process.cwd(); 15 | const fileName = path.relative(root, argv.name); 16 | info(`Reading: ${fileName}`); 17 | 18 | const testNameRegex = /[^\/]+(?=\.)/g; 19 | const testName = fileName.match(testNameRegex)[0]; 20 | 21 | const program = ts.createProgram([fileName], {}); 22 | const checker = program.getTypeChecker(); 23 | const source = program.getSourceFile(fileName); 24 | 25 | if (!source) { 26 | error('Typescript source file is required'); 27 | return; 28 | } 29 | 30 | ts.forEachChild(source, (node) => { 31 | // TODO: get also function and class, extract data from there like e.g. default values 32 | if (ts.isInterfaceDeclaration(node)) { 33 | const symbol = checker.getSymbolAtLocation(node.name); 34 | const data = [] as TestPropsInfo[]; 35 | symbol.members.forEach((loc) => { 36 | const info = serializeSymbol(loc, checker); 37 | data.push(info); 38 | }); 39 | generateTestFile(data, testName, fileName, { saveToSameFolder }); 40 | } 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/serializeSymbol.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.serializeSymbol = serializeSymbol; 7 | 8 | var ts = _interopRequireWildcard(require("typescript")); 9 | 10 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } 11 | 12 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 13 | 14 | function serializeSymbol(symbol, checker) { 15 | return { 16 | name: symbol.getName(), 17 | documentation: ts.displayPartsToString(symbol.getDocumentationComment(checker)), 18 | type: checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)), 19 | // @ts-ignore 20 | required: !symbol.valueDeclaration.questionToken 21 | }; 22 | } 23 | //# sourceMappingURL=serializeSymbol.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@patys/snap-it", 3 | "version": "0.3.0", 4 | "description": "Tool to generate jest test cases with snapshots for React Native components.", 5 | "main": "index.ts", 6 | "author": "Patryk Szczygło", 7 | "license": "MIT", 8 | "bin": { 9 | "snap-it": "bin/snap-it.js" 10 | }, 11 | "publishConfig": { 12 | "access": "public", 13 | "registry": "https://registry.npmjs.org/" 14 | }, 15 | "scripts": { 16 | "build": "babel --extensions .ts,.tsx src --out-dir lib --ignore '**/__tests__/**' --source-maps --delete-dir-on-start", 17 | "start": "yarn build && node lib/index.js", 18 | "lint": "./node_modules/.bin/tslint --project ./tsconfig.json", 19 | "release": "release-it" 20 | }, 21 | "devDependencies": { 22 | "@babel/cli": "^7.12.1", 23 | "@commitlint/cli": "^11.0.0", 24 | "@commitlint/config-conventional": "^11.0.0", 25 | "@release-it/conventional-changelog": "^2.0.0", 26 | "@types/jest": "^25.1.2", 27 | "@types/node": "^14.14.6", 28 | "husky": "^4.3.0", 29 | "prettier": "2.1.2", 30 | "release-it": "^14.2.1" 31 | }, 32 | "dependencies": { 33 | "@babel/core": "^7.12.3", 34 | "@babel/plugin-proposal-class-properties": "^7.12.1", 35 | "@babel/preset-env": "^7.12.1", 36 | "@babel/preset-flow": "^7.12.1", 37 | "@babel/preset-react": "^7.12.5", 38 | "@babel/preset-typescript": "^7.12.1", 39 | "chalk": "^4.1.0", 40 | "ejs": "^3.1.5", 41 | "fs-extra": "^9.0.1", 42 | "ts-jest": "^26.4.3", 43 | "tsc-watch": "^4.2.9", 44 | "tslint": "^6.1.3", 45 | "typescript": "^4.0.5", 46 | "yargs": "^16.1.0" 47 | }, 48 | "husky": { 49 | "hooks": { 50 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/generateTestFile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.generateTestFile = generateTestFile; 7 | 8 | var _path = _interopRequireDefault(require("path")); 9 | 10 | var _ejs = _interopRequireDefault(require("ejs")); 11 | 12 | var _fsExtra = _interopRequireDefault(require("fs-extra")); 13 | 14 | var _logger = require("./utils/logger"); 15 | 16 | var _getProps = require("./getProps"); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 19 | 20 | const TEMPLATE = _path.default.resolve(__dirname, '../template/template'); 21 | 22 | async function generateTestFile(data, filename, filepath, options) { 23 | const root = process.cwd(); 24 | 25 | let dest = _path.default.relative(root, '__tests__'); 26 | 27 | if (options.saveToSameFolder) { 28 | console.log(filepath); 29 | dest = _path.default.relative(root, filepath.match(/(.*)[\/\\]/)[1] || ''); 30 | } 31 | 32 | const filepathRelative = options.saveToSameFolder ? `./${_path.default.relative(dest, filepath)}` : _path.default.relative(dest, filepath); 33 | await _fsExtra.default.mkdirp(dest); 34 | const target = `${dest}/${filename}.test.tsx`; 35 | const content = await _fsExtra.default.readFile(TEMPLATE, 'utf8'); 36 | (0, _logger.info)(`Writing to ${target}`); 37 | await _fsExtra.default.writeFile(target, _ejs.default.render(content, { 38 | data, 39 | getRequiredProps: _getProps.getRequiredProps, 40 | getOptionalPropsArray: _getProps.getOptionalPropsArray, 41 | getPropValue: _getProps.getPropValue, 42 | componentName: filename, 43 | filepath: filepathRelative 44 | })); 45 | (0, _logger.success)(`Done, check your test in ${target}`); 46 | } 47 | //# sourceMappingURL=generateTestFile.js.map -------------------------------------------------------------------------------- /lib/getProps.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.getRequiredProps = getRequiredProps; 7 | exports.getOptionalPropsArray = getOptionalPropsArray; 8 | exports.getPropValue = getPropValue; 9 | 10 | var _functionUtils = require("./utils/functionUtils"); 11 | 12 | var _getStringType = _interopRequireDefault(require("./types/getStringType")); 13 | 14 | var _getNumberType = _interopRequireDefault(require("./types/getNumberType")); 15 | 16 | var _getBooleanType = _interopRequireDefault(require("./types/getBooleanType")); 17 | 18 | var _getObjectType = _interopRequireDefault(require("./types/getObjectType")); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | function getRequiredProps(data) { 23 | let requriedString = ''; 24 | 25 | for (let i = 0; i < data.length; i++) { 26 | if (data[i].required) { 27 | requriedString += `${data[i].name}: ${getPropValue(data[i])},\n`; 28 | } 29 | } 30 | 31 | return requriedString; 32 | } // TODO: move generating this data to be similar to getRequiredProps so we can get rid of implementation inside template (template/template) 33 | 34 | 35 | function getOptionalPropsArray(data) { 36 | return data.filter(d => !d.required); 37 | } 38 | 39 | function getPropValue(data) { 40 | const type = data.type; 41 | 42 | if ((0, _functionUtils.isFunction)(type)) { 43 | return 'jest.fn()'; 44 | } // TODO: move all of this to separate functions like getStringType 45 | 46 | 47 | switch (type) { 48 | case 'string': 49 | return `'${(0, _getStringType.default)(data.name)}'`; 50 | 51 | case 'number': 52 | return `'${(0, _getNumberType.default)()}'`; 53 | 54 | case 'boolean': 55 | return `'${(0, _getBooleanType.default)()}'`; 56 | 57 | case 'object': 58 | return `'${(0, _getObjectType.default)()}'`; 59 | 60 | case 'any': 61 | return `'${(0, _getObjectType.default)()}'`; 62 | 63 | default: 64 | return 'undefined'; 65 | } 66 | } 67 | //# sourceMappingURL=getProps.js.map -------------------------------------------------------------------------------- /lib/getProps.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/getProps.ts"],"names":["getRequiredProps","data","requriedString","i","length","required","name","getPropValue","getOptionalPropsArray","filter","d","type"],"mappings":";;;;;;;;;AAAA;;AACA;;AACA;;AACA;;AACA;;;;AAEO,SAASA,gBAAT,CAA0BC,IAA1B,EAAgC;AACrC,MAAIC,cAAc,GAAG,EAArB;;AACA,OAAK,IAAIC,CAAC,GAAG,CAAb,EAAgBA,CAAC,GAAGF,IAAI,CAACG,MAAzB,EAAiCD,CAAC,EAAlC,EAAsC;AACpC,QAAIF,IAAI,CAACE,CAAD,CAAJ,CAAQE,QAAZ,EAAsB;AACpBH,MAAAA,cAAc,IAAK,GAAED,IAAI,CAACE,CAAD,CAAJ,CAAQG,IAAK,KAAIC,YAAY,CAACN,IAAI,CAACE,CAAD,CAAL,CAAU,KAA5D;AACD;AACF;;AACD,SAAOD,cAAP;AACD,C,CAED;;;AACO,SAASM,qBAAT,CAA+BP,IAA/B,EAAqC;AAC1C,SAAOA,IAAI,CAACQ,MAAL,CAAaC,CAAD,IAAO,CAACA,CAAC,CAACL,QAAtB,CAAP;AACD;;AAEM,SAASE,YAAT,CAAsBN,IAAtB,EAA4B;AACjC,QAAMU,IAAI,GAAGV,IAAI,CAACU,IAAlB;;AAEA,MAAI,+BAAWA,IAAX,CAAJ,EAAsB;AACpB,WAAO,WAAP;AACD,GALgC,CAMjC;;;AACA,UAAQA,IAAR;AACE,SAAK,QAAL;AACE,aAAQ,IAAG,4BAAcV,IAAI,CAACK,IAAnB,CAAyB,GAApC;;AACF,SAAK,QAAL;AACE,aAAQ,IAAG,6BAAgB,GAA3B;;AACF,SAAK,SAAL;AACE,aAAQ,IAAG,8BAAiB,GAA5B;;AACF,SAAK,QAAL;AACE,aAAQ,IAAG,6BAAgB,GAA3B;;AACF,SAAK,KAAL;AACE,aAAQ,IAAG,6BAAgB,GAA3B;;AACF;AACE,aAAO,WAAP;AAZJ;AAcD","sourcesContent":["import { isFunction } from './utils/functionUtils';\nimport getStringType from './types/getStringType';\nimport getNumberType from './types/getNumberType';\nimport getBooleanType from './types/getBooleanType';\nimport getObjectType from './types/getObjectType';\n\nexport function getRequiredProps(data) {\n let requriedString = '';\n for (let i = 0; i < data.length; i++) {\n if (data[i].required) {\n requriedString += `${data[i].name}: ${getPropValue(data[i])},\\n`;\n }\n }\n return requriedString;\n}\n\n// TODO: move generating this data to be similar to getRequiredProps so we can get rid of implementation inside template (template/template)\nexport function getOptionalPropsArray(data) {\n return data.filter((d) => !d.required);\n}\n\nexport function getPropValue(data) {\n const type = data.type;\n\n if (isFunction(type)) {\n return 'jest.fn()';\n }\n // TODO: move all of this to separate functions like getStringType\n switch (type) {\n case 'string':\n return `'${getStringType(data.name)}'`;\n case 'number':\n return `'${getNumberType()}'`;\n case 'boolean':\n return `'${getBooleanType()}'`;\n case 'object':\n return `'${getObjectType()}'`;\n case 'any':\n return `'${getObjectType()}'`;\n default:\n return 'undefined';\n }\n}\n"],"file":"getProps.js"} -------------------------------------------------------------------------------- /example/Search.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-native-testing-library'; 3 | 4 | import { Search } from './Search.tsx'; 5 | 6 | describe('Search', () => { 7 | test('Snaphot for required props', () => { 8 | const props = { 9 | requiredTest: 'testing string', 10 | 11 | } 12 | const tree = render().toJSON(); 13 | expect(tree).toMatchSnapshot(); 14 | }); 15 | 16 | test('Snaphot for numberTest', () => { 17 | const props = { 18 | requiredTest: 'testing string', 19 | 20 | numberTest: '123', 21 | } 22 | const tree = render().toJSON(); 23 | expect(tree).toMatchSnapshot(); 24 | }); 25 | 26 | test('Snaphot for booleanTest', () => { 27 | const props = { 28 | requiredTest: 'testing string', 29 | 30 | booleanTest: 'true', 31 | } 32 | const tree = render().toJSON(); 33 | expect(tree).toMatchSnapshot(); 34 | }); 35 | 36 | test('Snaphot for stringTest', () => { 37 | const props = { 38 | requiredTest: 'testing string', 39 | 40 | stringTest: 'testing string', 41 | } 42 | const tree = render().toJSON(); 43 | expect(tree).toMatchSnapshot(); 44 | }); 45 | 46 | test('Snaphot for anyTest', () => { 47 | const props = { 48 | requiredTest: 'testing string', 49 | 50 | anyTest: '{}', 51 | } 52 | const tree = render().toJSON(); 53 | expect(tree).toMatchSnapshot(); 54 | }); 55 | 56 | test('Snaphot for functionTest', () => { 57 | const props = { 58 | requiredTest: 'testing string', 59 | 60 | functionTest: jest.fn(), 61 | } 62 | const tree = render().toJSON(); 63 | expect(tree).toMatchSnapshot(); 64 | }); 65 | 66 | test('Snaphot for color', () => { 67 | const props = { 68 | requiredTest: 'testing string', 69 | 70 | color: '#ffffff', 71 | } 72 | const tree = render().toJSON(); 73 | expect(tree).toMatchSnapshot(); 74 | }); 75 | 76 | test('Snaphot for backgroundColor', () => { 77 | const props = { 78 | requiredTest: 'testing string', 79 | 80 | backgroundColor: '#ffffff', 81 | } 82 | const tree = render().toJSON(); 83 | expect(tree).toMatchSnapshot(); 84 | }); 85 | 86 | test('Snaphot for bgColor', () => { 87 | const props = { 88 | requiredTest: 'testing string', 89 | 90 | bgColor: '#ffffff', 91 | } 92 | const tree = render().toJSON(); 93 | expect(tree).toMatchSnapshot(); 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _path = _interopRequireDefault(require("path")); 4 | 5 | var ts = _interopRequireWildcard(require("typescript")); 6 | 7 | var _yargs = _interopRequireDefault(require("yargs")); 8 | 9 | var _logger = require("./utils/logger"); 10 | 11 | var _serializeSymbol = require("./serializeSymbol"); 12 | 13 | var _generateTestFile = require("./generateTestFile"); 14 | 15 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } 16 | 17 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | _yargs.default.command('g ', 'create a snapshot for component', {}, snapshot).argv; 22 | 23 | async function snapshot(argv) { 24 | const saveToSameFolder = argv.direct === 'true'; 25 | const root = process.cwd(); 26 | 27 | const fileName = _path.default.relative(root, argv.name); 28 | 29 | (0, _logger.info)(`Reading: ${fileName}`); 30 | const testNameRegex = /[^\/]+(?=\.)/g; 31 | const testName = fileName.match(testNameRegex)[0]; 32 | const program = ts.createProgram([fileName], {}); 33 | const checker = program.getTypeChecker(); 34 | const source = program.getSourceFile(fileName); 35 | 36 | if (!source) { 37 | (0, _logger.error)('Typescript source file is required'); 38 | return; 39 | } 40 | 41 | ts.forEachChild(source, node => { 42 | // TODO: get also function and class, extract data from there like e.g. default values 43 | if (ts.isInterfaceDeclaration(node)) { 44 | const symbol = checker.getSymbolAtLocation(node.name); 45 | const data = []; 46 | symbol.members.forEach(loc => { 47 | const info = (0, _serializeSymbol.serializeSymbol)(loc, checker); 48 | data.push(info); 49 | }); 50 | (0, _generateTestFile.generateTestFile)(data, testName, fileName, { 51 | saveToSameFolder 52 | }); 53 | } 54 | }); 55 | } 56 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/generateTestFile.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/generateTestFile.ts"],"names":["TEMPLATE","path","resolve","__dirname","generateTestFile","data","filename","filepath","options","root","process","cwd","dest","relative","saveToSameFolder","console","log","match","filepathRelative","fs","mkdirp","target","content","readFile","writeFile","ejs","render","getRequiredProps","getOptionalPropsArray","getPropValue","componentName"],"mappings":";;;;;;;AAAA;;AACA;;AACA;;AAEA;;AAEA;;;;AAMA,MAAMA,QAAQ,GAAGC,cAAKC,OAAL,CAAaC,SAAb,EAAwB,sBAAxB,CAAjB;;AAEO,eAAeC,gBAAf,CACLC,IADK,EAELC,QAFK,EAGLC,QAHK,EAILC,OAJK,EAKL;AACA,QAAMC,IAAI,GAAGC,OAAO,CAACC,GAAR,EAAb;;AAEA,MAAIC,IAAI,GAAGX,cAAKY,QAAL,CAAcJ,IAAd,EAAoB,WAApB,CAAX;;AACA,MAAID,OAAO,CAACM,gBAAZ,EAA8B;AAC5BC,IAAAA,OAAO,CAACC,GAAR,CAAYT,QAAZ;AACAK,IAAAA,IAAI,GAAGX,cAAKY,QAAL,CAAcJ,IAAd,EAAoBF,QAAQ,CAACU,KAAT,CAAe,YAAf,EAA6B,CAA7B,KAAmC,EAAvD,CAAP;AACD;;AACD,QAAMC,gBAAgB,GAAGV,OAAO,CAACM,gBAAR,GACpB,KAAIb,cAAKY,QAAL,CAAcD,IAAd,EAAoBL,QAApB,CAA8B,EADd,GAErBN,cAAKY,QAAL,CAAcD,IAAd,EAAoBL,QAApB,CAFJ;AAGA,QAAMY,iBAAGC,MAAH,CAAUR,IAAV,CAAN;AAEA,QAAMS,MAAM,GAAI,GAAET,IAAK,IAAGN,QAAS,WAAnC;AACA,QAAMgB,OAAO,GAAG,MAAMH,iBAAGI,QAAH,CAAYvB,QAAZ,EAAsB,MAAtB,CAAtB;AAEA,oBAAM,cAAaqB,MAAO,EAA1B;AACA,QAAMF,iBAAGK,SAAH,CACJH,MADI,EAEJI,aAAIC,MAAJ,CAAWJ,OAAX,EAAoB;AAClBjB,IAAAA,IADkB;AAElBsB,IAAAA,gBAAgB,EAAhBA,0BAFkB;AAGlBC,IAAAA,qBAAqB,EAArBA,+BAHkB;AAIlBC,IAAAA,YAAY,EAAZA,sBAJkB;AAKlBC,IAAAA,aAAa,EAAExB,QALG;AAMlBC,IAAAA,QAAQ,EAAEW;AANQ,GAApB,CAFI,CAAN;AAWA,uBAAS,4BAA2BG,MAAO,EAA3C;AACD","sourcesContent":["import path from 'path';\nimport ejs from 'ejs';\nimport fs from 'fs-extra';\n\nimport { info, success } from './utils/logger';\nimport { TestPropsInfo } from './serializeSymbol';\nimport {\n getRequiredProps,\n getOptionalPropsArray,\n getPropValue,\n} from './getProps';\n\nconst TEMPLATE = path.resolve(__dirname, '../template/template');\n\nexport async function generateTestFile(\n data: TestPropsInfo[],\n filename: string,\n filepath: string,\n options: { saveToSameFolder: boolean }\n) {\n const root = process.cwd();\n\n let dest = path.relative(root, '__tests__');\n if (options.saveToSameFolder) {\n console.log(filepath);\n dest = path.relative(root, filepath.match(/(.*)[\\/\\\\]/)[1] || '');\n }\n const filepathRelative = options.saveToSameFolder\n ? `./${path.relative(dest, filepath)}`\n : path.relative(dest, filepath);\n await fs.mkdirp(dest);\n\n const target = `${dest}/${filename}.test.tsx`;\n const content = await fs.readFile(TEMPLATE, 'utf8');\n\n info(`Writing to ${target}`);\n await fs.writeFile(\n target,\n ejs.render(content, {\n data,\n getRequiredProps,\n getOptionalPropsArray,\n getPropValue,\n componentName: filename,\n filepath: filepathRelative,\n })\n );\n success(`Done, check your test in ${target}`);\n}\n"],"file":"generateTestFile.js"} -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/index.ts"],"names":["yargs","command","snapshot","argv","saveToSameFolder","direct","root","process","cwd","fileName","path","relative","name","testNameRegex","testName","match","program","ts","createProgram","checker","getTypeChecker","source","getSourceFile","forEachChild","node","isInterfaceDeclaration","symbol","getSymbolAtLocation","data","members","forEach","loc","info","push"],"mappings":";;AAAA;;AACA;;AACA;;AAEA;;AACA;;AACA;;;;;;;;AAEAA,eAAMC,OAAN,CAAc,UAAd,EAA0B,iCAA1B,EAA6D,EAA7D,EAAiEC,QAAjE,EAA2EC,IAA3E;;AAEA,eAAeD,QAAf,CAAwBC,IAAxB,EAAoD;AAClD,QAAMC,gBAAgB,GAAGD,IAAI,CAACE,MAAL,KAAgB,MAAzC;AAEA,QAAMC,IAAI,GAAGC,OAAO,CAACC,GAAR,EAAb;;AACA,QAAMC,QAAQ,GAAGC,cAAKC,QAAL,CAAcL,IAAd,EAAoBH,IAAI,CAACS,IAAzB,CAAjB;;AACA,oBAAM,YAAWH,QAAS,EAA1B;AAEA,QAAMI,aAAa,GAAG,eAAtB;AACA,QAAMC,QAAQ,GAAGL,QAAQ,CAACM,KAAT,CAAeF,aAAf,EAA8B,CAA9B,CAAjB;AAEA,QAAMG,OAAO,GAAGC,EAAE,CAACC,aAAH,CAAiB,CAACT,QAAD,CAAjB,EAA6B,EAA7B,CAAhB;AACA,QAAMU,OAAO,GAAGH,OAAO,CAACI,cAAR,EAAhB;AACA,QAAMC,MAAM,GAAGL,OAAO,CAACM,aAAR,CAAsBb,QAAtB,CAAf;;AAEA,MAAI,CAACY,MAAL,EAAa;AACX,uBAAM,oCAAN;AACA;AACD;;AAEDJ,EAAAA,EAAE,CAACM,YAAH,CAAgBF,MAAhB,EAAyBG,IAAD,IAAU;AAChC;AACA,QAAIP,EAAE,CAACQ,sBAAH,CAA0BD,IAA1B,CAAJ,EAAqC;AACnC,YAAME,MAAM,GAAGP,OAAO,CAACQ,mBAAR,CAA4BH,IAAI,CAACZ,IAAjC,CAAf;AACA,YAAMgB,IAAI,GAAG,EAAb;AACAF,MAAAA,MAAM,CAACG,OAAP,CAAeC,OAAf,CAAwBC,GAAD,IAAS;AAC9B,cAAMC,IAAI,GAAG,sCAAgBD,GAAhB,EAAqBZ,OAArB,CAAb;AACAS,QAAAA,IAAI,CAACK,IAAL,CAAUD,IAAV;AACD,OAHD;AAIA,8CAAiBJ,IAAjB,EAAuBd,QAAvB,EAAiCL,QAAjC,EAA2C;AAAEL,QAAAA;AAAF,OAA3C;AACD;AACF,GAXD;AAYD","sourcesContent":["import path from 'path';\nimport * as ts from 'typescript';\nimport yargs from 'yargs';\n\nimport { info, error } from './utils/logger';\nimport { serializeSymbol, TestPropsInfo } from './serializeSymbol';\nimport { generateTestFile } from './generateTestFile';\n\nyargs.command('g ', 'create a snapshot for component', {}, snapshot).argv;\n\nasync function snapshot(argv: yargs.Arguments) {\n const saveToSameFolder = argv.direct === 'true';\n\n const root = process.cwd();\n const fileName = path.relative(root, argv.name);\n info(`Reading: ${fileName}`);\n\n const testNameRegex = /[^\\/]+(?=\\.)/g;\n const testName = fileName.match(testNameRegex)[0];\n\n const program = ts.createProgram([fileName], {});\n const checker = program.getTypeChecker();\n const source = program.getSourceFile(fileName);\n\n if (!source) {\n error('Typescript source file is required');\n return;\n }\n\n ts.forEachChild(source, (node) => {\n // TODO: get also function and class, extract data from there like e.g. default values\n if (ts.isInterfaceDeclaration(node)) {\n const symbol = checker.getSymbolAtLocation(node.name);\n const data = [] as TestPropsInfo[];\n symbol.members.forEach((loc) => {\n const info = serializeSymbol(loc, checker);\n data.push(info);\n });\n generateTestFile(data, testName, fileName, { saveToSameFolder });\n }\n });\n}\n"],"file":"index.js"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snap-it 2 | 3 | [![npm version](https://badge.fury.io/js/%40patys%2Fsnap-it.svg)](https://badge.fury.io/js/%40patys%2Fsnap-it) 4 | 5 | This is a tool to create a snapshot for your component. You can use it to simply generate boilerplate file with all cases. Keep in mind that you should verify and add your own test cases. 6 | 7 | Works only with TypeScript for now. 8 | 9 | ## Usage: 10 | 11 | Follow instalation guide and then: 12 | 13 | ```bash 14 | yarn create-snapshot components/Search.tsx 15 | ``` 16 | 17 | Or you can use it directly without installing: 18 | 19 | ```bash 20 | npx @patys/snap-it g components/Search.tsx 21 | ``` 22 | 23 | To save in the same directory use option: --direct=true 24 | 25 | ```bash 26 | npx @patys/snap-it g components/Search.tsx --direct=true 27 | ``` 28 | 29 | Effect with direct: 30 | components/Search.tsx 31 | components/Search.test.tsx 32 | 33 | Effect without direct: 34 | components/Search.tsx 35 | \_\_tests\_\_/Search.test.tsx 36 | 37 | ## Installation: 38 | 39 | ```bash 40 | yarn add --dev @patys/snap-it 41 | ``` 42 | 43 | Add to your package.json 44 | 45 | ```bash 46 | { 47 | "scripts": { 48 | ... 49 | "create-snapshot": "yarn snap-it g " 50 | ... 51 | } 52 | } 53 | ``` 54 | 55 | ## Example: 56 | 57 | Your component: 58 | 59 | ```javascript 60 | // example/Search.tsx 61 | import React from 'react'; 62 | import { View } from 'react-native'; 63 | 64 | interface Props { 65 | numberTest?: number; 66 | booleanTest?: boolean; 67 | stringTest?: string; 68 | anyTest?: any; 69 | functionTest?: () => void; 70 | 71 | requiredTest: string; 72 | } 73 | 74 | export default function Search({}: Props) { 75 | return ; 76 | } 77 | ``` 78 | 79 | Effect: 80 | 81 | ```javascript 82 | import React from 'react'; 83 | import { render } from '@testing-library/react-native'; 84 | 85 | import { Search } from '../example/Search.tsx'; 86 | 87 | describe('Search', () => { 88 | test('Snaphot for required props', () => { 89 | const props = { 90 | requiredTest: 'testing string', 91 | }; 92 | const tree = render().toJSON(); 93 | expect(tree).toMatchSnapshot(); 94 | }); 95 | 96 | test('Snaphot for numberTest', () => { 97 | const props = { 98 | requiredTest: 'testing string', 99 | 100 | numberTest: 123, 101 | }; 102 | const tree = render().toJSON(); 103 | expect(tree).toMatchSnapshot(); 104 | }); 105 | 106 | test('Snaphot for booleanTest', () => { 107 | const props = { 108 | requiredTest: 'testing string', 109 | 110 | booleanTest: true, 111 | }; 112 | const tree = render().toJSON(); 113 | expect(tree).toMatchSnapshot(); 114 | }); 115 | 116 | test('Snaphot for stringTest', () => { 117 | const props = { 118 | requiredTest: 'testing string', 119 | 120 | stringTest: 'testing string', 121 | }; 122 | const tree = render().toJSON(); 123 | expect(tree).toMatchSnapshot(); 124 | }); 125 | 126 | test('Snaphot for anyTest', () => { 127 | const props = { 128 | requiredTest: 'testing string', 129 | 130 | anyTest: {}, 131 | }; 132 | const tree = render().toJSON(); 133 | expect(tree).toMatchSnapshot(); 134 | }); 135 | 136 | test('Snaphot for functionTest', () => { 137 | const props = { 138 | requiredTest: 'testing string', 139 | 140 | functionTest: jest.fn(), 141 | }; 142 | const tree = render().toJSON(); 143 | expect(tree).toMatchSnapshot(); 144 | }); 145 | }); 146 | ``` 147 | --------------------------------------------------------------------------------