diff --git a/src/arrayTaskValidation.ts b/src/arrayTaskValidation.ts deleted file mode 100644 index 413ac4d6..00000000 --- a/src/arrayTaskValidation.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { either, readonlyArray, task, taskEither } from 'fp-ts'; -import type { Either } from 'fp-ts/Either'; -import { pipe } from 'fp-ts/function'; -import type { Task } from 'fp-ts/Task'; -import type { TaskEither } from 'fp-ts/TaskEither'; - -export const lift = (te: TaskEither) => pipe(te, taskEither.mapLeft(readonlyArray.of)); - -export const run = (resultsTask: Task[]>) => - pipe( - resultsTask, - task.map( - readonlyArray.sequence(either.getApplicativeValidation(readonlyArray.getSemigroup())) - ) - ); diff --git a/src/index.ts b/src/index.ts index 6ca11527..eea980db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ import { console } from 'fp-ts'; -import { logErrorsF } from './logErrorsF'; +import { logF } from './log'; import { runTests } from './runTests'; import { test } from './test'; -export { logErrorsF, runTests, test }; +export { logF, runTests, test }; -export const logErrors = logErrorsF({ console }); +export const log = logF({ console }); diff --git a/src/logErrorsF.ts b/src/log.ts similarity index 90% rename from src/logErrorsF.ts rename to src/log.ts index 053a7943..b4939579 100644 --- a/src/logErrorsF.ts +++ b/src/log.ts @@ -6,7 +6,7 @@ import * as std from 'fp-ts-std'; import c from 'picocolors'; import { match } from 'ts-pattern'; -import type { Change, TestError, TestFailedResult } from './type'; +import type { Change, TestError, TestFailResult } from './type'; const getPrefix = (changeType: Change['type']) => match(changeType) @@ -57,7 +57,7 @@ const indent = flow( readonlyArray.intercalate(string.Monoid)('\n') ); -const formatErrorResult = (errorResult: TestFailedResult) => [ +const formatErrorResult = (errorResult: TestFailResult) => [ `${c.red(c.bold(c.inverse(' FAIL ')))} ${errorResult.name}`, c.red(c.bold(`${errorResult.error.code}:`)), '', @@ -65,9 +65,9 @@ const formatErrorResult = (errorResult: TestFailedResult) => [ '', ]; -export const logErrorsF = +export const logF = (env: { readonly console: { readonly log: (str: string) => IO } }) => - (res: TaskEither) => + (res: TaskEither) => pipe( res, taskEither.swap, diff --git a/src/runTests.ts b/src/runTests.ts index 16f68bdb..7b9f3999 100644 --- a/src/runTests.ts +++ b/src/runTests.ts @@ -1,15 +1,13 @@ import { boolean, either, readonlyArray, task, taskEither } from 'fp-ts'; import type { Either } from 'fp-ts/Either'; -import { pipe } from 'fp-ts/function'; +import { identity, pipe } from 'fp-ts/function'; import type { Task } from 'fp-ts/Task'; import type { TaskEither } from 'fp-ts/TaskEither'; import * as std from 'fp-ts-std'; import * as retry from 'retry-ts'; import { retrying } from 'retry-ts/lib/Task'; -import { modify } from 'spectacles-ts'; import { match } from 'ts-pattern'; -import * as arrayTaskValidation from './arrayTaskValidation'; import { getDiffs } from './getDiffs'; import type { Assertion, @@ -18,8 +16,10 @@ import type { MultipleAssertionTest, Test, TestConfig, + TestEitherResult, TestError, - TestFailedResult, + TestPassResult, + TestsResult, } from './type'; const hasAnyChange = readonlyArray.foldMap(boolean.MonoidAll)((diff: Change) => diff.type === '0'); @@ -37,8 +37,7 @@ const assertEqual = (result: { readonly expected: unknown; readonly actual: unkn pipe( result, getDiffs, - either.chainW(either.fromPredicate(hasAnyChange, assertionFailed(result))), - either.map(() => undefined) + either.chainW(either.fromPredicate(hasAnyChange, assertionFailed(result))) ); const unhandledException = (exception: unknown) => ({ @@ -56,10 +55,9 @@ const runActualAndAssert = (param: { ); const runWithTimeout = - (assertion: Pick) => - (te: TaskEither): TaskEither => + (assertion: Pick) => (te: TaskEither) => task - .getRaceMonoid>() + .getRaceMonoid>() .concat( te, pipe({ code: 'timed out' as const }, taskEither.left, task.delay(assertion.timeout ?? 5000)) @@ -70,13 +68,15 @@ const runWithRetry = (te: TaskEither) => retrying(test.retry ?? retry.limitRetries(0), () => te, either.isLeft); -const runAssertion = (assertion: Assertion) => +const runAssertion = (assertion: Assertion): Task => pipe( runActualAndAssert({ actualTask: assertion.act, expectedResult: assertion.assert }), runWithTimeout({ timeout: assertion.timeout }), runWithRetry({ retry: assertion.retry }), - taskEither.mapLeft((error) => ({ name: assertion.name, error })), - arrayTaskValidation.lift + taskEither.bimap( + (error) => [either.left({ name: assertion.name, error })], + () => [{ name: assertion.name }] + ) ); const runWithConcurrency = (config: { readonly concurrency?: Concurrency }) => @@ -86,21 +86,55 @@ const runWithConcurrency = (config: { readonly concurrency?: Concurrency }) => .with({ type: 'sequential' }, () => readonlyArray.sequence(task.ApplicativeSeq)) .exhaustive(); -const runMultipleAssertion = ( - test: MultipleAssertionTest -): TaskEither => +const foldTestResult = (r: readonly TestsResult[]): TestsResult => + pipe( + r, + readonlyArray.reduce( + either.right([]), + (acc, el) => + pipe( + acc, + either.chain( + (accr): TestsResult => + pipe( + el, + either.bimap( + (ell) => + pipe(accr, readonlyArray.map(either.right), (accra) => [...accra, ...ell]), + (elr) => [...accr, ...elr] + ) + ) + ), + either.mapLeft((accl): readonly TestEitherResult[] => + pipe(el, either.match(identity, readonlyArray.map(either.right)), (newAcc) => [ + ...accl, + ...newAcc, + ]) + ) + ) + ) + ); + +const prependName = + (name: string) => + (k: K): K => ({ + ...k, + name: std.string.prepend(`${name} > `), + }); + +const runMultipleAssertion = (test: MultipleAssertionTest): Task => pipe( test.asserts, readonlyArray.map(runAssertion), runWithConcurrency({ concurrency: test.concurrency }), - arrayTaskValidation.run, + task.map(foldTestResult), taskEither.bimap( - readonlyArray.map(modify('name', std.string.prepend(`${test.name} > `))), - () => undefined + readonlyArray.map(either.bimap(prependName(test.name), prependName(test.name))), + readonlyArray.map(prependName(test.name)) ) ); -const runTest = (test: Test): TaskEither => +const runTest = (test: Test): Task => match(test) .with({ assertion: 'single' }, ({ assert }) => runAssertion(assert)) .with({ assertion: 'multiple' }, runMultipleAssertion) @@ -108,11 +142,10 @@ const runTest = (test: Test): TaskEither export const runTests = (testsWithConfig: TestConfig) => - (tests: readonly Test[]): TaskEither => + (tests: readonly Test[]): Task => pipe( tests, readonlyArray.map(runTest), runWithConcurrency({ concurrency: testsWithConfig.concurrency }), - arrayTaskValidation.run, - taskEither.map(() => undefined) + task.map(foldTestResult) ); diff --git a/src/type.ts b/src/type.ts index 3cddf05b..880a0a2a 100644 --- a/src/type.ts +++ b/src/type.ts @@ -1,3 +1,4 @@ +import type { Either } from 'fp-ts/Either'; import type { Task } from 'fp-ts/Task'; import type * as retry from 'retry-ts'; @@ -55,7 +56,15 @@ export type TestError = readonly exception: unknown; }; -export type TestFailedResult = { +export type TestFailResult = { readonly name: string; readonly error: TestError; }; + +export type TestPassResult = { + readonly name: string; +}; + +export type TestEitherResult = Either; + +export type TestsResult = Either; diff --git a/test/index.ts b/test/index.ts index fae8d1c2..580cbe85 100644 --- a/test/index.ts +++ b/test/index.ts @@ -4,16 +4,10 @@ import { pipe } from 'fp-ts/function'; import * as src from '../src'; import * as srcNode from '../src/node'; import * as exit from './exitF'; -import * as logErrors from './logErrorsF'; +import * as log from './log'; import * as primitive from './primitive'; import * as timeout from './timeout'; -const tests = [logErrors.tests, exit.tests, timeout.tests, primitive.tests]; +const tests = [log.tests, exit.tests, timeout.tests, primitive.tests]; -export const main = pipe( - tests, - readonlyArray.flatten, - src.runTests({}), - src.logErrors, - srcNode.exit -); +export const main = pipe(tests, readonlyArray.flatten, src.runTests({}), src.log, srcNode.exit); diff --git a/test/logErrorsF.ts b/test/log.ts similarity index 97% rename from test/logErrorsF.ts rename to test/log.ts index 7b883b21..0129dd7c 100644 --- a/test/logErrorsF.ts +++ b/test/log.ts @@ -1,7 +1,7 @@ import { ioRef, readonlyArray, string, task } from 'fp-ts'; import { pipe } from 'fp-ts/function'; -import { logErrorsF, runTests, test } from '../src'; +import { logF, runTests, test } from '../src'; import { testW } from '../src/test'; const green = '\x1b[32m'; @@ -38,7 +38,7 @@ const caseToTest = (tc: Case) => }), ], runTests({}), - logErrorsF({ console: { log: logRef.write } }) + logF({ console: { log: logRef.write } }) ) ), task.chainIOK((logRef) => logRef.read),