chore: refactor and simplify testing (#39050)

This commit is contained in:
Oliver Eyton-Williams
2020-06-13 11:27:15 +02:00
committed by GitHub
parent 5934984064
commit b4926052f4
14 changed files with 122 additions and 305 deletions

View File

@ -1,206 +0,0 @@
// originally based off of https://github.com/gulpjs/vinyl
import invariant from 'invariant';
import { Observable } from 'rx';
import castToObservable from '../../server/utils/cast-to-observable';
// createFileStream(
// files: [...PolyVinyl]
// ) => Observable[...Observable[...PolyVinyl]]
export function createFileStream(files = []) {
return Observable.of(Observable.from(files));
}
// Observable::pipe(
// project(
// file: PolyVinyl
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
// ) => Observable[...Observable[...PolyVinyl]]
export function pipe(project) {
const source = this;
return source.map(files =>
files.flatMap(file => castToObservable(project(file)))
);
}
// interface PolyVinyl {
// source: String,
// contents: String,
// name: String,
// ext: String,
// path: String,
// key: String,
// head: String,
// tail: String,
// history: [...String],
// error: Null|Object|Error
// }
// createPoly({
// name: String,
// ext: String,
// contents: String,
// history?: [...String],
// }) => PolyVinyl, throws
export function createPoly({ name, ext, contents, history, ...rest } = {}) {
invariant(typeof name === 'string', 'name must be a string but got %s', name);
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
invariant(
typeof contents === 'string',
'contents must be a string but got %s',
contents
);
return {
...rest,
history: Array.isArray(history) ? history : [name + ext],
name,
ext,
path: name + '.' + ext,
key: name + ext,
contents,
error: null
};
}
// isPoly(poly: Any) => Boolean
export function isPoly(poly) {
return (
poly &&
typeof poly.contents === 'string' &&
typeof poly.name === 'string' &&
typeof poly.ext === 'string' &&
Array.isArray(poly.history)
);
}
// checkPoly(poly: Any) => Void, throws
export function checkPoly(poly) {
invariant(
isPoly(poly),
'function should receive a PolyVinyl, but got %s',
poly
);
}
// isEmpty(poly: PolyVinyl) => Boolean, throws
export function isEmpty(poly) {
checkPoly(poly);
return !!poly.contents;
}
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
// setContent will loose source if set
export function setContent(contents, poly) {
checkPoly(poly);
return {
...poly,
contents,
source: null
};
}
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
export function setExt(ext, poly) {
checkPoly(poly);
const newPoly = {
...poly,
ext,
path: poly.name + '.' + ext,
key: poly.name + ext
};
newPoly.history = [...poly.history, newPoly.path];
return newPoly;
}
// setName(name: String, poly: PolyVinyl) => PolyVinyl
export function setName(name, poly) {
checkPoly(poly);
const newPoly = {
...poly,
name,
path: name + '.' + poly.ext,
key: name + poly.ext
};
newPoly.history = [...poly.history, newPoly.path];
return newPoly;
}
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
export function setError(error, poly) {
invariant(
typeof error === 'object',
'error must be an object or null, but got %',
error
);
checkPoly(poly);
return {
...poly,
error
};
}
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
export function clearHeadTail(poly) {
checkPoly(poly);
return {
...poly,
head: '',
tail: ''
};
}
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
export function appendToTail(tail, poly) {
checkPoly(poly);
return {
...poly,
tail: poly.tail.concat(tail)
};
}
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
export function compileHeadTail(padding = '', poly) {
return clearHeadTail(
transformContents(
() => [poly.head, poly.contents, poly.tail].join(padding),
poly
)
);
}
// transformContents(
// wrap: (contents: String) => String,
// poly: PolyVinyl
// ) => PolyVinyl
// transformContents will keep a copy of the original
// code in the `source` property. If the original polyvinyl
// already contains a source, this version will continue as
// the source property
export function transformContents(wrap, poly) {
const newPoly = setContent(wrap(poly.contents), poly);
// if no source exist, set the original contents as source
newPoly.source = poly.source || poly.contents;
return newPoly;
}
// transformHeadTailAndContents(
// wrap: (source: String) => String,
// poly: PolyVinyl
// ) => PolyVinyl
export function transformHeadTailAndContents(wrap, poly) {
return {
...transformContents(wrap, poly),
head: wrap(poly.head),
tail: wrap(poly.tail)
};
}
export function testContents(predicate, poly) {
return !!predicate(poly.contents);
}
export function updateFileFromSpec(spec, poly) {
return setContent(poly.contents, createPoly(spec));
}

View File

@ -1,5 +1,5 @@
import validator from 'express-validator'; import validator from 'express-validator';
import { isPoly } from '../../common/utils/polyvinyl'; import { isPoly } from '../../../utils/polyvinyl';
const isObject = val => !!val && typeof val === 'object'; const isObject = val => !!val && typeof val === 'object';

View File

@ -3,7 +3,7 @@ const env = require('../config/env');
const { createFilePath } = require('gatsby-source-filesystem'); const { createFilePath } = require('gatsby-source-filesystem');
const { dasherize } = require('../utils/slugs'); const { dasherize } = require('../utils/slugs');
const { blockNameify } = require('./utils/blockNameify'); const { blockNameify } = require('../utils/block-nameify');
const { const {
createChallengePages, createChallengePages,
createBlockIntroPages, createBlockIntroPages,

View File

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap'; import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
import { Spacer } from '../helpers'; import { Spacer } from '../helpers';
import { blockNameify } from '../../../utils/blockNameify'; import { blockNameify } from '../../../../utils/block-nameify';
import Heart from '../../assets/icons/Heart'; import Heart from '../../assets/icons/Heart';
import Cup from '../../assets/icons/Cup'; import Cup from '../../assets/icons/Cup';
import MinimalDonateForm from './MinimalDonateForm'; import MinimalDonateForm from './MinimalDonateForm';

View File

@ -8,7 +8,7 @@ import { Link } from 'gatsby';
import { makeExpandedBlockSelector, toggleBlock } from '../redux'; import { makeExpandedBlockSelector, toggleBlock } from '../redux';
import { completedChallengesSelector, executeGA } from '../../../redux'; import { completedChallengesSelector, executeGA } from '../../../redux';
import Caret from '../../../assets/icons/Caret'; import Caret from '../../../assets/icons/Caret';
import { blockNameify } from '../../../../utils/blockNameify'; import { blockNameify } from '../../../../../utils/block-nameify';
import GreenPass from '../../../assets/icons/GreenPass'; import GreenPass from '../../../assets/icons/GreenPass';
import GreenNotCompleted from '../../../assets/icons/GreenNotCompleted'; import GreenNotCompleted from '../../../assets/icons/GreenNotCompleted';
import IntroInformation from '../../../assets/icons/IntroInformation'; import IntroInformation from '../../../assets/icons/IntroInformation';

View File

@ -8,7 +8,11 @@ import {
template as _template template as _template
} from 'lodash'; } from 'lodash';
import { compileHeadTail, setExt, transformContents } from '../utils/polyvinyl'; import {
compileHeadTail,
setExt,
transformContents
} from '../../../../../utils/polyvinyl';
const htmlCatch = '\n<!--fcc-->\n'; const htmlCatch = '\n<!--fcc-->\n';
const jsCatch = '\n;/*fcc*/\n'; const jsCatch = '\n;/*fcc*/\n';

View File

@ -12,7 +12,7 @@ import {
import protect from '@freecodecamp/loop-protect'; import protect from '@freecodecamp/loop-protect';
import * as vinyl from '../utils/polyvinyl.js'; import * as vinyl from '../../../../../utils/polyvinyl.js';
import createWorker from '../utils/worker-executor'; import createWorker from '../utils/worker-executor';
// the config files are created during the build, but not before linting // the config files are created during the build, but not before linting

View File

@ -14,7 +14,7 @@ import {
import { types as appTypes } from '../../../redux'; import { types as appTypes } from '../../../redux';
import { setContent, isPoly } from '../utils/polyvinyl'; import { setContent, isPoly } from '../../../../../utils/polyvinyl';
import { createFlashMessage } from '../../../components/Flash/redux'; import { createFlashMessage } from '../../../components/Flash/redux';

View File

@ -2,7 +2,7 @@ import { createAction, handleActions } from 'redux-actions';
import { createTypes } from '../../../../utils/stateManagement'; import { createTypes } from '../../../../utils/stateManagement';
import { createPoly } from '../utils/polyvinyl'; import { createPoly } from '../../../../../utils/polyvinyl';
import challengeModalEpic from './challenge-modal-epic'; import challengeModalEpic from './challenge-modal-epic';
import completionEpic from './completion-epic'; import completionEpic from './completion-epic';
import codeLockEpic from './code-lock-epic'; import codeLockEpic from './code-lock-epic';

View File

@ -7,7 +7,7 @@ const {
} = require('../../curriculum/getChallenges'); } = require('../../curriculum/getChallenges');
const { dasherize, nameify } = require('../../utils/slugs'); const { dasherize, nameify } = require('../../utils/slugs');
const { locale } = require('../config/env.json'); const { locale } = require('../config/env.json');
const { blockNameify } = require('./blockNameify'); const { blockNameify } = require('../../utils/block-nameify');
const arrToString = arr => const arrToString = arr =>
Array.isArray(arr) ? arr.join('\n') : _.toString(arr); Array.isArray(arr) ? arr.join('\n') : _.toString(arr);

View File

@ -6,8 +6,6 @@ const fs = require('fs');
const { dasherize } = require('../utils/slugs'); const { dasherize } = require('../utils/slugs');
const { challengeSchemaValidator } = require('./schema/challengeSchema');
const challengesDir = path.resolve(__dirname, './challenges'); const challengesDir = path.resolve(__dirname, './challenges');
const metaDir = path.resolve(challengesDir, '_meta'); const metaDir = path.resolve(challengesDir, '_meta');
exports.challengesDir = challengesDir; exports.challengesDir = challengesDir;
@ -38,13 +36,13 @@ exports.getChallengesForLang = function getChallengesForLang(lang) {
readDirP({ root: getChallengesDirForLang(lang) }) readDirP({ root: getChallengesDirForLang(lang) })
.on('data', file => { .on('data', file => {
running++; running++;
buildCurriculum(file, curriculum, lang).then(done); buildCurriculum(file, curriculum).then(done);
}) })
.on('end', done); .on('end', done);
}); });
}; };
async function buildCurriculum(file, curriculum, lang) { async function buildCurriculum(file, curriculum) {
const { name, depth, path: filePath, fullPath, stat } = file; const { name, depth, path: filePath, fullPath, stat } = file;
if (depth === 1 && stat.isDirectory()) { if (depth === 1 && stat.isDirectory()) {
// extract the superBlock info // extract the superBlock info
@ -80,12 +78,12 @@ async function buildCurriculum(file, curriculum, lang) {
} }
const { meta } = challengeBlock; const { meta } = challengeBlock;
const challenge = await createChallenge(fullPath, meta, lang); const challenge = await createChallenge(fullPath, meta);
challengeBlock.challenges = [...challengeBlock.challenges, challenge]; challengeBlock.challenges = [...challengeBlock.challenges, challenge];
} }
async function createChallenge(fullPath, maybeMeta, lang) { async function createChallenge(fullPath, maybeMeta) {
let meta; let meta;
if (maybeMeta) { if (maybeMeta) {
meta = maybeMeta; meta = maybeMeta;
@ -98,11 +96,6 @@ async function createChallenge(fullPath, maybeMeta, lang) {
} }
const { name: superBlock } = superBlockInfoFromFullPath(fullPath); const { name: superBlock } = superBlockInfoFromFullPath(fullPath);
const challenge = await parseMarkdown(fullPath); const challenge = await parseMarkdown(fullPath);
const result = challengeSchemaValidator(lang)(challenge);
if (result.error) {
console.log(result.value);
throw new Error(result.error);
}
const challengeOrder = findIndex( const challengeOrder = findIndex(
meta.challengeOrder, meta.challengeOrder,
([id]) => id === challenge.id ([id]) => id === challenge.id

View File

@ -53,9 +53,7 @@ const {
buildJSChallenge buildJSChallenge
} = require('../../client/src/templates/Challenges/utils/build'); } = require('../../client/src/templates/Challenges/utils/build');
const { const { createPoly } = require('../../utils/polyvinyl');
createPoly
} = require('../../client/src/templates/Challenges/utils/polyvinyl');
const testEvaluator = require('../../client/config/test-evaluator').filename; const testEvaluator = require('../../client/config/test-evaluator').filename;
@ -323,24 +321,24 @@ function populateTestsForLang({ lang, challenges }, meta) {
}); });
let { files = [] } = challenge; let { files = [] } = challenge;
let createTestRunner;
if (challengeType === challengeTypes.backend) { if (challengeType === challengeTypes.backend) {
it('Check tests is not implemented.'); it('Check tests is not implemented.');
return; return;
} else if ( }
challengeType === challengeTypes.js ||
challengeType === challengeTypes.bonfire if (files.length > 1) {
) {
createTestRunner = createTestRunnerForJSChallenge;
} else if (files.length === 1) {
createTestRunner = createTestRunnerForDOMChallenge;
} else {
it('Check tests.', () => { it('Check tests.', () => {
throw new Error('Seed file should be only the one.'); throw new Error('Seed file should be only the one.');
}); });
return; return;
} }
const buildChallenge =
challengeType === challengeTypes.js ||
challengeType === challengeTypes.bonfire
? buildJSChallenge
: buildDOMChallenge;
files = files.map(createPoly); files = files.map(createPoly);
it('Test suite must fail on the initial contents', async function() { it('Test suite must fail on the initial contents', async function() {
this.timeout(5000 * tests.length + 1000); this.timeout(5000 * tests.length + 1000);
@ -353,7 +351,7 @@ function populateTestsForLang({ lang, challenges }, meta) {
testRunner = await createTestRunner( testRunner = await createTestRunner(
{ ...challenge, files }, { ...challenge, files },
'', '',
page buildChallenge
); );
} catch { } catch {
fails = true; fails = true;
@ -390,7 +388,7 @@ function populateTestsForLang({ lang, challenges }, meta) {
const testRunner = await createTestRunner( const testRunner = await createTestRunner(
{ ...challenge, files }, { ...challenge, files },
solution, solution,
page buildChallenge
); );
for (const test of tests) { for (const test of tests) {
await testRunner(test); await testRunner(test);
@ -404,72 +402,75 @@ function populateTestsForLang({ lang, challenges }, meta) {
}); });
} }
async function createTestRunnerForDOMChallenge( async function createTestRunner(
{ required = [], template, files }, { required = [], template, files },
solution, solution,
context buildChallenge
) { ) {
if (solution) { if (solution) {
files[0].contents = solution; files[0].contents = solution;
} }
const { build, sources, loadEnzyme } = await buildDOMChallenge({ const { build, sources, loadEnzyme } = await buildChallenge({
files, files,
required, required,
template template
}); });
await context.reload();
await context.setContent(build);
await context.evaluate(
async (sources, loadEnzyme) => {
const code = sources && 'index' in sources ? sources['index'] : ''; const code = sources && 'index' in sources ? sources['index'] : '';
const evaluator = await (buildChallenge === buildDOMChallenge
? getContextEvaluator(build, sources, code, loadEnzyme)
: getWorkerEvaluator(build, sources, code));
return async ({ text, testString }) => {
try {
const { pass, err } = await evaluator.evaluate(testString, 5000);
if (!pass) {
throw new AssertionError(err.message);
}
} catch (err) {
reThrow(err, text);
}
};
}
async function getContextEvaluator(build, sources, code, loadEnzyme) {
await initializeTestRunner(build, sources, code, loadEnzyme);
return {
evaluate: async (testString, timeout) =>
Promise.race([
new Promise((_, reject) =>
setTimeout(() => reject('timeout'), timeout)
),
await page.evaluate(async testString => {
return await document.__runTest(testString);
}, testString)
])
};
}
async function getWorkerEvaluator(build, sources, code) {
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
return {
evaluate: async (testString, timeout) =>
await testWorker.execute({ testString, build, code, sources }, timeout)
.done
};
}
async function initializeTestRunner(build, sources, code, loadEnzyme) {
await page.reload();
await page.setContent(build);
await page.evaluate(
async (code, sources, loadEnzyme) => {
const getUserInput = fileName => sources[fileName]; const getUserInput = fileName => sources[fileName];
await document.__initTestFrame({ code, getUserInput, loadEnzyme }); await document.__initTestFrame({ code, getUserInput, loadEnzyme });
}, },
code,
sources, sources,
loadEnzyme loadEnzyme
); );
return async ({ text, testString }) => {
try {
const { pass, err } = await Promise.race([
new Promise((_, reject) => setTimeout(() => reject('timeout'), 5000)),
await context.evaluate(async testString => {
return await document.__runTest(testString);
}, testString)
]);
if (!pass) {
throw new AssertionError(err.message);
}
} catch (err) {
reThrow(err, text);
}
};
}
async function createTestRunnerForJSChallenge({ files }, solution) {
if (solution) {
files[0].contents = solution;
}
const { build, sources } = await buildJSChallenge({ files });
const code = sources && 'index' in sources ? sources['index'] : '';
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
return async ({ text, testString }) => {
try {
const { pass, err } = await testWorker.execute(
{ testString, build, code, sources },
5000
).done;
if (!pass) {
throw new AssertionError(err.message);
}
} catch (err) {
reThrow(err, text);
}
};
} }
function reThrow(err, text) { function reThrow(err, text) {

View File

@ -1,14 +1,17 @@
// originally based off of https://github.com/gulpjs/vinyl // originally based off of https://github.com/gulpjs/vinyl
import invariant from 'invariant'; const invariant = require('invariant');
import { of, from, isObservable } from 'rxjs'; const { of, from, isObservable } = require('rxjs');
import { map, switchMap } from 'rxjs/operators'; const { map, switchMap } = require('rxjs/operators');
export const isPromise = value => function isPromise(value) {
return (
value && value &&
typeof value.subscribe !== 'function' && typeof value.subscribe !== 'function' &&
typeof value.then === 'function'; typeof value.then === 'function'
);
}
export function castToObservable(maybe) { function castToObservable(maybe) {
if (isObservable(maybe)) { if (isObservable(maybe)) {
return maybe; return maybe;
} }
@ -21,7 +24,7 @@ export function castToObservable(maybe) {
// createFileStream( // createFileStream(
// files: [...PolyVinyl] // files: [...PolyVinyl]
// ) => Observable[...Observable[...PolyVinyl]] // ) => Observable[...Observable[...PolyVinyl]]
export function createFileStream(files = []) { function createFileStream(files = []) {
return of(from(files)); return of(from(files));
} }
@ -30,7 +33,7 @@ export function createFileStream(files = []) {
// file: PolyVinyl // file: PolyVinyl
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl] // ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
// ) => Observable[...Observable[...PolyVinyl]] // ) => Observable[...Observable[...PolyVinyl]]
export function pipe(project) { function pipe(project) {
const source = this; const source = this;
return source.pipe( return source.pipe(
map(files => { map(files => {
@ -58,7 +61,7 @@ export function pipe(project) {
// contents: String, // contents: String,
// history?: [...String], // history?: [...String],
// }) => PolyVinyl, throws // }) => PolyVinyl, throws
export function createPoly({ name, ext, contents, history, ...rest } = {}) { function createPoly({ name, ext, contents, history, ...rest } = {}) {
invariant(typeof name === 'string', 'name must be a string but got %s', name); invariant(typeof name === 'string', 'name must be a string but got %s', name);
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext); invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
@ -82,7 +85,7 @@ export function createPoly({ name, ext, contents, history, ...rest } = {}) {
} }
// isPoly(poly: Any) => Boolean // isPoly(poly: Any) => Boolean
export function isPoly(poly) { function isPoly(poly) {
return ( return (
poly && poly &&
typeof poly.contents === 'string' && typeof poly.contents === 'string' &&
@ -93,7 +96,7 @@ export function isPoly(poly) {
} }
// checkPoly(poly: Any) => Void, throws // checkPoly(poly: Any) => Void, throws
export function checkPoly(poly) { function checkPoly(poly) {
invariant( invariant(
isPoly(poly), isPoly(poly),
'function should receive a PolyVinyl, but got %s', 'function should receive a PolyVinyl, but got %s',
@ -102,14 +105,14 @@ export function checkPoly(poly) {
} }
// isEmpty(poly: PolyVinyl) => Boolean, throws // isEmpty(poly: PolyVinyl) => Boolean, throws
export function isEmpty(poly) { function isEmpty(poly) {
checkPoly(poly); checkPoly(poly);
return !!poly.contents; return !!poly.contents;
} }
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl // setContent(contents: String, poly: PolyVinyl) => PolyVinyl
// setContent will loose source if set // setContent will loose source if set
export function setContent(contents, poly) { function setContent(contents, poly) {
checkPoly(poly); checkPoly(poly);
return { return {
...poly, ...poly,
@ -119,7 +122,7 @@ export function setContent(contents, poly) {
} }
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl // setExt(ext: String, poly: PolyVinyl) => PolyVinyl
export function setExt(ext, poly) { function setExt(ext, poly) {
checkPoly(poly); checkPoly(poly);
const newPoly = { const newPoly = {
...poly, ...poly,
@ -132,7 +135,7 @@ export function setExt(ext, poly) {
} }
// setName(name: String, poly: PolyVinyl) => PolyVinyl // setName(name: String, poly: PolyVinyl) => PolyVinyl
export function setName(name, poly) { function setName(name, poly) {
checkPoly(poly); checkPoly(poly);
const newPoly = { const newPoly = {
...poly, ...poly,
@ -145,7 +148,7 @@ export function setName(name, poly) {
} }
// setError(error: Object, poly: PolyVinyl) => PolyVinyl // setError(error: Object, poly: PolyVinyl) => PolyVinyl
export function setError(error, poly) { function setError(error, poly) {
invariant( invariant(
typeof error === 'object', typeof error === 'object',
'error must be an object or null, but got %', 'error must be an object or null, but got %',
@ -159,7 +162,7 @@ export function setError(error, poly) {
} }
// clearHeadTail(poly: PolyVinyl) => PolyVinyl // clearHeadTail(poly: PolyVinyl) => PolyVinyl
export function clearHeadTail(poly) { function clearHeadTail(poly) {
checkPoly(poly); checkPoly(poly);
return { return {
...poly, ...poly,
@ -169,7 +172,7 @@ export function clearHeadTail(poly) {
} }
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl // appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
export function appendToTail(tail, poly) { function appendToTail(tail, poly) {
checkPoly(poly); checkPoly(poly);
return { return {
...poly, ...poly,
@ -178,7 +181,7 @@ export function appendToTail(tail, poly) {
} }
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl // compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
export function compileHeadTail(padding = '', poly) { function compileHeadTail(padding = '', poly) {
return clearHeadTail( return clearHeadTail(
transformContents( transformContents(
() => [poly.head, poly.contents, poly.tail].join(padding), () => [poly.head, poly.contents, poly.tail].join(padding),
@ -195,7 +198,7 @@ export function compileHeadTail(padding = '', poly) {
// code in the `source` property. If the original polyvinyl // code in the `source` property. If the original polyvinyl
// already contains a source, this version will continue as // already contains a source, this version will continue as
// the source property // the source property
export function transformContents(wrap, poly) { function transformContents(wrap, poly) {
const newPoly = setContent(wrap(poly.contents), poly); const newPoly = setContent(wrap(poly.contents), poly);
// if no source exist, set the original contents as source // if no source exist, set the original contents as source
newPoly.source = poly.source || poly.contents; newPoly.source = poly.source || poly.contents;
@ -206,7 +209,7 @@ export function transformContents(wrap, poly) {
// wrap: (source: String) => String, // wrap: (source: String) => String,
// poly: PolyVinyl // poly: PolyVinyl
// ) => PolyVinyl // ) => PolyVinyl
export function transformHeadTailAndContents(wrap, poly) { function transformHeadTailAndContents(wrap, poly) {
return { return {
...transformContents(wrap, poly), ...transformContents(wrap, poly),
head: wrap(poly.head), head: wrap(poly.head),
@ -214,10 +217,32 @@ export function transformHeadTailAndContents(wrap, poly) {
}; };
} }
export function testContents(predicate, poly) { function testContents(predicate, poly) {
return !!predicate(poly.contents); return !!predicate(poly.contents);
} }
export function updateFileFromSpec(spec, poly) { function updateFileFromSpec(spec, poly) {
return setContent(poly.contents, createPoly(spec)); return setContent(poly.contents, createPoly(spec));
} }
module.exports = {
isPromise,
castToObservable,
createFileStream,
pipe,
createPoly,
isPoly,
checkPoly,
isEmpty,
setContent,
setExt,
setName,
setError,
clearHeadTail,
appendToTail,
compileHeadTail,
transformContents,
transformHeadTailAndContents,
testContents,
updateFileFromSpec
};