Skip to content

Commit

Permalink
Add basic matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
ElijahKotyluk committed Nov 30, 2023
1 parent 1bf8163 commit 1d8afc7
Show file tree
Hide file tree
Showing 53 changed files with 2,962 additions and 2,448 deletions.
10 changes: 10 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,13 @@ coverage --instrument_test_targets
# rather than user.bazelrc as suggested in the Bazel docs)

try-import %workspace%/.bazelrc.user

# honor the setting of `skipLibCheck` in the tsconfig.json file
build --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig
fetch --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig
query --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig

# Use "tsc" as the transpiler when ts_project has no `transpiler` set.
build --@aspect_rules_ts//ts:default_to_tsc_transpiler
fetch --@aspect_rules_ts//ts:default_to_tsc_transpiler
query --@aspect_rules_ts//ts:default_to_tsc_transpiler
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module.exports = {
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
"@typescript-eslint/no-namespace": "off"
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/ban-types": "off",
}
},
],
Expand Down
3 changes: 3 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ ts_project(
tsconfig = {},
deps = [
"//:node_modules/@jest/types",
"//:node_modules/jest-cli",
"//:node_modules/jest-junit",
"//:node_modules/@types/node",
],
)
18 changes: 10 additions & 8 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ workspace(
# How this workspace would be referenced with absolute labels from another workspace
name = "onyx",
# Map the @npm bazel workspace to the node_modules directory. This lets Bazel use the same node_modules as other local tooling.
managed_directories = {"@npm": ["node_modules"]},
# managed_directories = {"@npm": ["node_modules"]},
)

load("//tools:deps.bzl", "fetch_build_bazel_rules_nodejs")
Expand All @@ -13,16 +13,18 @@ load("@aspect_rules_js//js:repositories.bzl", "rules_js_dependencies")

rules_js_dependencies()

load("@aspect_rules_ts//ts:repositories.bzl", "LATEST_VERSION", "rules_ts_dependencies")
load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies")

rules_ts_dependencies(ts_version = LATEST_VERSION)
rules_ts_dependencies(
# This keeps the TypeScript version in-sync with the editor, which is typically best.
ts_version_from = "//:package.json",
# ts_version = "5.3.0",
ts_integrity = "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
)

load("@aspect_rules_jest//jest:repositories.bzl", "jest_repositories", JEST_LATEST_VERSION = "LATEST_VERSION")
load("@aspect_rules_jest//jest:dependencies.bzl", "rules_jest_dependencies")

jest_repositories(
name = "jest",
jest_version = JEST_LATEST_VERSION,
)
rules_jest_dependencies()

load("@rules_nodejs//nodejs:repositories.bzl", "DEFAULT_NODE_VERSION", "nodejs_register_toolchains")

Expand Down
5 changes: 3 additions & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ const config: Config.InitialOptions = {
moduleFileExtensions: ['ts', 'js'],
preset: "ts-jest",
testMatch: [
"**/*.spec.js",
"**/*.spec.ts"
"**/test/*.spec.js",
"**/test/*.spec.ts"
],
testEnvironment: "jsdom",
}

export default config
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,25 @@
"bazel:test": "bazel test //...",
"lint": "eslint . -c ./.eslintrc.js --ignore-path .eslintignore --ext ts --ext js",
"lint:fix": "eslint . -c ./.eslintrc.js --ignore-path .eslintignore --ext ts --ext js --fix",
"test": "jest -c jest.config.js",
"setup:deps": "bazel run -- @pnpm//:pnpm -C $PWD i"
"setup:deps": "bazel run -- @pnpm//:pnpm -C $PWD i",
"outdated:deps": "bazel run -- @pnpm//:pnpm -C $PWD outdated"
},
"devDependencies": {
"@bazel/bazelisk": "latest",
"@bazel/buildifier": "latest",
"@bazel/ibazel": "latest",
"@bazel/bazelisk": "1.19.0",
"@bazel/buildifier": "6.4.0",
"@bazel/ibazel": "0.23.7",
"@jest/types": "^29.2.1",
"@types/jest": "^27.5.2",
"@types/node": "^18.11.3",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"eslint": "^8.26.0",
"husky": "^7.0.1",
"jest": "^27.5.1",
"jest": "^29.0.0",
"jest-cli": "^28.0.1",
"jest-junit": "^16.0.0",
"lint-staged": "^11.1.2",
"ts-jest": "^27.0.4",
"typescript": "^4.3.5"
"ts-jest": "^29.1.1",
"typescript": "5.3.2"
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Matchers
import { matchers, AnyMatchers, onyx } from './matchers'
import { matchers, AnyMatchers, BuiltInMatchers, onyx, _Matchers } from './matchers'

type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : never

type Expectations<M extends AnyMatchers> = { [K in keyof M]: OmitFirstArg<M[K]> }
type Expectations<M extends BuiltInMatchers> = { [K in keyof M]: OmitFirstArg<M[K]> }

export type NegatedExpectations<M extends AnyMatchers> = Expectations<M> & {
export type NegatedExpectations<M extends onyx.Matchers> = Expectations<M> & {
not: Expectations<M>
}

Expand Down Expand Up @@ -41,32 +41,33 @@ interface IExpectPass<P> {

type ExpectationResult<F, P> = IExpectFail<F> | IExpectPass<P>

export function expectations<M extends AnyMatchers = AnyMatchers>(
export function expectations<M extends _Matchers>(
currentMatchers: M,
expectation: any,
expectation: unknown,
not = false,
): Expectations<M> {
const entries = Object.entries(currentMatchers)
.map(([key, value]) => [
key,
(...args: any[]): boolean => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const result = value(expectation, ...args)
if (result === not) { throw new ExpectError(`${not ? 'not.' : ''}${key} failed`) } // TODO diff
return result
},
])
const entries = Object.entries(currentMatchers)
.map(([key, value]) => [
key,
(...args: any[]): boolean => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
(...args: unknown[]): boolean => {
const result = value(expectation, ...args)
if (result === not) { throw new ExpectError(`${not ? 'not.' : ''}${key} failed`) } // TODO diff
return result
},
])
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment
return Object.assign({}, ...Array.from(entries, ([k, v]: any[]) => ({[k]: v}) ))
}


export function expect<M extends onyx.Matchers> (
expectation: any,
export function expect<M extends _Matchers> (
expectation: unknown,
): NegatedExpectations<M> {
return {
...expectations<M>(matchers as M, expectation, false),
not: expectations<M>(matchers as M, expectation, true),
...expectations<M>(matchers, expectation, false),
not: expectations<M>(matchers, expectation, true),
}
}

Expand Down
Empty file.
29 changes: 29 additions & 0 deletions packages/match/matchers/test/toBe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect as onyxExpect } from '../src/Expectation'

describe('toBe', () => {
const pass: any[] = [
[1, 1, true],
['1', '1', true],
[null, null, true],
[undefined, undefined, true],
]

const failMsg = 'onyxToBe failed'

const fail: any[] = [
['1', 1, failMsg],
[{ a: 1 }, { b: 2 }, failMsg],
[{ a: 1 }, { a: 1 }, failMsg],
[[1, 2], [2, 3], failMsg],
[[], [], failMsg],
[{}, {}, failMsg],
]

test.each(pass)('toBe(%p, %p) should return true', (a, b, expected) => {
expect(onyxExpect(a).onyxToBe(b)).toBe(expected)
})

test.each(fail)('toBe(%p, %p) should throw an expect error', (a, b, expected: string) => {
expect(() => onyxExpect(a).onyxToBe(b)).toThrowError(expected)
})
})
30 changes: 30 additions & 0 deletions packages/match/matchers/test/toHaveLength.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect as onyxExpect } from '../src/Expectation'

describe('toBe', () => {
const pass: any[] = [
[[1, 2, 3], 3, true],
['1', 1, true],
// [{ a: 1, b: 2, c: 3 }, 3, true],
// [(() => [1, 2, 3, 4, 5])(), 5, true],
// [() => [1, 2, 3, 4, 5], 5, true],
]

const failMsg = 'onyxToHaveLength failed'

const fail: any[] = [
['1', 0, failMsg],
[true, { b: 2 }, failMsg],
[false, { a: 1 }, failMsg],
[[1, 2], [2, 3], failMsg],
[[], [], failMsg],
[{}, {}, failMsg],
]

test.each(pass)('toHaveLength(%p, %p) should return true', (a, b: number, expected) => {
expect(onyxExpect(a).onyxToHaveLength(b)).toBe(expected)
})

test.each(fail)('toHaveLength(%p, %p) should throw an expect error', (a, b: number, expected: string) => {
expect(() => onyxExpect(a).onyxToHaveLength(b)).toThrowError(expected)
})
})
15 changes: 15 additions & 0 deletions packages/match/matchers/toBe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { MatcherResult, MatcherFn } from './'

export type ToBe = MatcherFn<'toBe'>

export const toBe: ToBe = function (received, expected) {

return {
actual: received,
expected,
message: '',
name: 'toBe',
passed: true,
result: false
}
}
5 changes: 5 additions & 0 deletions packages/match/matchers/toHaveLength.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const toHaveLength = (actual: unknown, expected: unknown) => {

}

export default toHaveLength
5 changes: 4 additions & 1 deletion packages/matchers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ ts_project(
"//:node_modules/@types/jest",
"//:node_modules/jest",
"//:node_modules/ts-jest",
"//:node_modules/jest-cli",
"//:node_modules/jest-junit",
],
)

jest_test(
name = "unit",
config = "//:jest_config",
data = [":tsconfig_test"],
data = [":tsconfig_test", "//:node_modules/@types/node"],
node_modules = "//:node_modules",
)
43 changes: 43 additions & 0 deletions packages/matchers/src/expect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AnyMatchers, matchers, onyx } from './matchers'

type OmitFirstArg<F> = F extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : never
type Expectations<M extends AnyMatchers> = {
[K in keyof M]: OmitFirstArg<M[K]>
}

export function expectations<M extends AnyMatchers = AnyMatchers>(
currentMatchers: M,
expectation: any,
not = false,
): Expectations<M> {
const entries = Object.entries(currentMatchers)
.map(([key, value]) => [
key,
(...args: any[]): boolean => {
const result = value(expectation, ...args)
if (result === not) { throw new ExpectError(`${not ? 'not.' : ''}${key} failed`) } // TODO diff
return result
},
])
return Object.assign({}, ...Array.from(entries, ([k, v]: any[]) => ({[k]: v}) ))
}

type NegatedExpectations<M extends AnyMatchers> = Expectations<M> & {
not: Expectations<M>
}

export default function expect<M extends onyx.Matchers = onyx.Matchers>(
expectation: any,
): NegatedExpectations<M> {
return {
...expectations<M>(matchers as M, expectation, false),
not: expectations<M>(matchers as M, expectation, true ),
}
}

export class ExpectError extends Error {
public constructor(message: string) {
super(message)
this.name = 'ExpectError'
}
}
2 changes: 2 additions & 0 deletions packages/matchers/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { matchers, AnyMatchers, extendMatchers, onyx } from './matchers'
export { default, ExpectError } from './expect'
41 changes: 41 additions & 0 deletions packages/matchers/src/internaltypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AnyMatchers, ExpectError } from './'

type OmitFirstArg<A> = A extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : never

type Expectations<M extends AnyMatchers> = { [K in keyof M]: OmitFirstArg<M[K]> }

export type NegatedExpectations<M extends AnyMatchers> = Expectations<M> & {
not: Expectations<M>
}


export type ExpectResult<A, E> = {
matcher: string
error?: ExpectError
status: ExpectStatus
actual: A
expected: E
}

enum ExpectStatus {
PASS = 'Pass',
FAIL = 'Fail',
}

const _expectFail = <F, P = never> (fail: F): ExpectationResult<F, P> => ({ _status: ExpectStatus.FAIL, fail })
const _expectPass = <P, F = never> (pass: P): ExpectationResult<F, P> => ({ _status: ExpectStatus.PASS, pass })

export const expectPass: <F = never, P = never> (pass: P) => ExpectationResult<F, P> = _expectPass
export const expectFail: <F = never, P = never> (fail: F) => ExpectationResult<F, P> = _expectFail

interface IExpectFail<F> {
readonly _status: ExpectStatus.FAIL
readonly fail: F
}

interface IExpectPass<P> {
readonly _status: ExpectStatus.PASS
readonly pass: P
}

type ExpectationResult<F, P> = IExpectFail<F> | IExpectPass<P>
Loading

0 comments on commit 1d8afc7

Please sign in to comment.