diff --git a/src/Vitest.res b/src/Vitest.res index 2f7d577..233aec5 100644 --- a/src/Vitest.res +++ b/src/Vitest.res @@ -968,6 +968,11 @@ external afterEachPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) let afterEachPromise = (~timeout=?, callback) => afterEachPromise(callback, timeout->Js.Undefined.fromOption) +@module("vitest") +external afterAll: (@uncurry (unit => 'a), Js.Undefined.t) => unit = "afterAll" + +let afterAll = (~timeout=?, callback) => afterAll(callback, timeout->Js.Undefined.fromOption) + @module("vitest") external afterAllPromise: (@uncurry (unit => promise<'a>), Js.Undefined.t) => unit = "afterAll" @@ -1109,7 +1114,9 @@ module Matchers = ( @inline let toMatch = (expected, list) => { - expected->dangerously_reinforce_assertion(Belt.List.toArray)->Array.toMatch(list->Belt.List.toArray) + expected + ->dangerously_reinforce_assertion(Belt.List.toArray) + ->Array.toMatch(list->Belt.List.toArray) } } @@ -1160,6 +1167,13 @@ module Assert = { %%private(@module("vitest") @val external assert_obj: t = "assert") + @module("vitest") + external assert_: (bool, Js.undefined) => unit = "assert" + let assert_ = (~message=?, value) => assert_(value, message->Js.Undefined.fromOption) + + @module("vitest") @scope("expect") + external unreachable: (~message: string=?, unit) => unit = "unreachable" + @send external equal: (t, 'a, 'a, Js.undefined) => unit = "equal" @inline @@ -1180,29 +1194,121 @@ module Vi = { @send external advanceTimersByTime: (t, int) => t = "advanceTimersByTime" @inline let advanceTimersByTime = ms => vi_obj->advanceTimersByTime(ms) + @send external advanceTimersByTimeAsync: (t, int) => promise = "advanceTimersByTimeAsync" + let advanceTimersByTimeAsync = time => vi_obj->advanceTimersByTimeAsync(time) + @send external advanceTimersToNextTimer: t => t = "advanceTimersToNextTimer" @inline let advanceTimersToNextTimer = () => vi_obj->advanceTimersToNextTimer + @send external advanceTimersToNextTimerAsync: t => promise = "advanceTimersToNextTimerAsync" + let advanceTimersToNextTimerAsync = () => vi_obj->advanceTimersToNextTimerAsync + + @send external getTimerCount: t => int = "getTimerCount" + let getTimerCount = () => vi_obj->getTimerCount + + @send external clearAllTimers: t => t = "clearAllTimers" + let clearAllTimers = () => vi_obj->clearAllTimers + + @send external runAllTicks: t => t = "runAllTicks" + let runAllTicks = () => vi_obj->runAllTicks + @send external runAllTimers: t => t = "runAllTimers" @inline let runAllTimers = () => vi_obj->runAllTimers + @send external runAllTimersAsync: t => promise = "runAllTimersAsync" + let runAllTimersAsync = () => vi_obj->runAllTimersAsync + @send external runOnlyPendingTimers: t => t = "runOnlyPendingTimers" @inline let runOnlyPendingTimers = () => vi_obj->runOnlyPendingTimers - @send external useFakeTimers: t => t = "useFakeTimers" - @inline let useFakeTimers = () => vi_obj->useFakeTimers + @send external runOnlyPendingTimersAsync: t => promise = "runOnlyPendingTimersAsync" + let runOnlyPendingTimersAsync = () => vi_obj->runOnlyPendingTimersAsync + + @send + external setSystemTime: (t, @unwrap [#Date(Js.Date.t) | #String(string) | #Int(int)]) => t = + "setSystemTime" + let setSystemTime = time => vi_obj->setSystemTime(time) + + /** + https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/sinonjs__fake-timers/index.d.ts + */ + type fakeTimersConfig = { + now?: Js.Date.t, // or int + toFake?: array, + loopLimit?: int, + shouldAdvanceTime?: bool, + advanceTimeDelta?: int, + shouldClearNativeTimers?: bool, + } + + @send external useFakeTimers: (t, ~config: fakeTimersConfig=?, unit) => t = "useFakeTimers" + @inline let useFakeTimers = (~config=?, ()) => vi_obj->useFakeTimers(~config?, ()) @send external useRealTimers: t => t = "useRealTimers" @inline let useRealTimers = () => vi_obj->useRealTimers - @send external mockCurrentDate: (t, Js.Date.t) => t = "mockCurrentDate" - @inline let mockCurrentDate = date => vi_obj->mockCurrentDate(date) + @send external isFakeTimers: t => bool = "isFakeTimers" + let isFakeTimers = () => vi_obj->isFakeTimers + + @send @return(nullable) + external getMockedSystemTime: t => option = "getMockedSystemTime" + let getMockedSystemTime = () => vi_obj->getMockedSystemTime + + @send external getRealSystemTime: t => float = "getRealSystemTime" + let getRealSystemTime = () => vi_obj->getRealSystemTime - @send external restoreCurrentDate: (t, Js.Date.t) => t = "restoreCurrentDate" - @inline let restoreCurrentDate = date => vi_obj->restoreCurrentDate(date) + type waitForOptions = { + timeout?: int, + interval?: int, + } + + @send external waitFor: (t, unit => 'a, waitForOptions) => promise<'a> = "waitFor" + + /** + @since(vitest >= 0.34.5) + */ + let waitFor = (callback, ~timeout=?, ~interval=?, ()) => { + waitFor(vi_obj, callback, {?timeout, ?interval}) + } + + @send + external waitForAsync: (t, unit => promise<'a>, waitForOptions) => promise<'a> = "waitFor" + + /** + @since(vitest >= 0.34.5) + */ + let waitForAsync = (callback, ~timeout=?, ~interval=?, ()) => { + waitForAsync(vi_obj, callback, {?timeout, ?interval}) + } + + type waitUntilOptions = { + timeout?: int, + interval?: int, + } + + @send + external waitUntil: (t, unit => 'a, waitUntilOptions) => promise<'a> = "waitUntil" + + /** + @since(vitest >= 0.34.5) + */ + let waitUntil = (callback, ~timeout=?, ~interval=?, ()) => { + waitUntil(vi_obj, callback, {?timeout, ?interval}) + } + + @send + external waitUntilAsync: (t, @uncurry unit => promise<'a>, waitUntilOptions) => promise<'a> = + "waitUntil" + + /** + @since(vitest >= 0.34.5) + */ + let waitUntilAsync = (callback, ~timeout=?, ~interval=?, ()) => { + waitUntilAsync(vi_obj, callback, {?timeout, ?interval}) + } - @send external getMockedDate: t => Js.null = "getMockedDate" - @inline let getMockedDate = () => vi_obj->getMockedDate->Js.Null.toOption + // binding this using vi_obj causes a runtime error. this is because vitest sees this inside this file, then tries to evaluate the hoisted function, but the hoisted function is not provided yet, it's just a parameter to the function + @send external hoisted: (t, unit => 'a) => 'a = "hoisted" } @scope("import.meta") @val diff --git a/tests/assertions.test.res b/tests/assertions.test.res index f76318c..128374e 100644 --- a/tests/assertions.test.res +++ b/tests/assertions.test.res @@ -1,5 +1,29 @@ open Vitest +describe("Assert", () => { + test("assert_", _t => { + Assert.assert_(true) + Assert.assert_(1 == 1) + Assert.assert_("one" == "one") + Assert.assert_(1 == 1) + }) + + test("unreachable", _t => { + try { + let _ = Js.Exn.raiseError("error") + Assert.unreachable() + } catch { + | _ => Assert.assert_(~message="threw error", true) + } + + try { + expect(true)->Expect.toBe(true) + } catch { + | _ => Assert.unreachable() + } + }) +}) + describe("Expect", () => { test("toBe", _t => { expect(1)->Expect.toBe(1) @@ -248,41 +272,41 @@ describe("Expect", () => { }) describe("Array", () => { - test( - "toContain", - _t => { - expect([1, 2, 3])->Expect.Array.toContain(2) - expect(["hello", "world"])->Expect.Array.toContain("world") - expect([true, false])->Expect.Array.toContain(false) - expect([1, 2, 3])->Expect.not->Expect.Array.toContain(4) + test( + "toContain", + _t => { + expect([1, 2, 3])->Expect.Array.toContain(2) + expect(["hello", "world"])->Expect.Array.toContain("world") + expect([true, false])->Expect.Array.toContain(false) + expect([1, 2, 3])->Expect.not->Expect.Array.toContain(4) }, ) test( "toContainEqual", _t => { - expect([1, 2, 3])->Expect.Array.toContainEqual(2) - expect(["hello", "world"])->Expect.Array.toContainEqual("world") - expect([true, false])->Expect.Array.toContainEqual(false) + expect([1, 2, 3])->Expect.Array.toContainEqual(2) + expect(["hello", "world"])->Expect.Array.toContainEqual("world") + expect([true, false])->Expect.Array.toContainEqual(false) expect([1, 2, 3])->Expect.not->Expect.Array.toContainEqual(4) }, ) test( "toHaveLength", - _t => { - expect([1, 2, 3])->Expect.Array.toHaveLength(3) - expect([])->Expect.Array.toHaveLength(0) - expect([1, 2, 3])->Expect.not->Expect.Array.toHaveLength(5) - }, - ) + _t => { + expect([1, 2, 3])->Expect.Array.toHaveLength(3) + expect([])->Expect.Array.toHaveLength(0) + expect([1, 2, 3])->Expect.not->Expect.Array.toHaveLength(5) + }, + ) - test( - "toMatch", - _t => { - expect([1, 2, 3])->Expect.Array.toMatch([1, 2, 3]) - expect(["hello", "world"])->Expect.Array.toMatch(["hello", "world"]) - expect([true, false])->Expect.Array.toMatch([true, false]) + test( + "toMatch", + _t => { + expect([1, 2, 3])->Expect.Array.toMatch([1, 2, 3]) + expect(["hello", "world"])->Expect.Array.toMatch(["hello", "world"]) + expect([true, false])->Expect.Array.toMatch([true, false]) expect([1, 2, 3])->Expect.not->Expect.Array.toMatch([1, 2]) }, ) diff --git a/tests/vi.test.res b/tests/vi.test.res new file mode 100644 index 0000000..3db462b --- /dev/null +++ b/tests/vi.test.res @@ -0,0 +1,171 @@ +open Vitest + +@val external nextTick: (unit => unit) => unit = "process.nextTick" + +describe("Vi", () => { + beforeEach(() => { + let _ = Vi.useRealTimers() + }) + + afterAll(() => { + let _ = Vi.useRealTimers() + }) + + let _promise = () => Js.Promise2.resolve() + + itAsync("should compile fake timers correctly", async _t => { + let _ = Vi.useFakeTimers() + Vi.isFakeTimers()->expect->Expect.toBe(true) + + let called = ref(false) + let called2 = ref(false) + + let _ = Js.Global.setTimeout(() => called := true, 100) + let _ = Js.Global.setTimeout(() => called2 := true, 200) + let _ = Vi.advanceTimersByTime(10) + called->expect->Expect.toEqual({contents: false}) + called2->expect->Expect.toEqual({contents: false}) + + let _ = Vi.advanceTimersByTime(100) + called->expect->Expect.toEqual({contents: true}) + called2->expect->Expect.toEqual({contents: false}) + called := false + + let _ = await Vi.advanceTimersByTimeAsync(1000) + called2->expect->Expect.toEqual({contents: true}) + called2 := false + Vi.getTimerCount()->expect->Expect.toBe(0) + + let _ = Js.Global.setTimeout(() => called := true, 1000) + let _ = Js.Global.setTimeout(() => called2 := true, 2000) + Vi.getTimerCount()->expect->Expect.toBe(2) + let _ = Vi.runAllTimers() + called->expect->Expect.toEqual({contents: true}) + called2->expect->Expect.toEqual({contents: true}) + called := false + called2 := false + + let _ = Js.Global.setTimeout(() => called := true, 1000) + let _ = Js.Global.setTimeout(() => called2 := true, 2000) + Vi.getTimerCount()->expect->Expect.toBe(2) + let _ = await Vi.runAllTimersAsync() + called->expect->Expect.toEqual({contents: true}) + called2->expect->Expect.toEqual({contents: true}) + called := false + called2 := false + + let _ = Js.Global.setTimeout(() => called := true, 1000) + Vi.getTimerCount()->expect->Expect.toBe(1) + let _ = Vi.runOnlyPendingTimers() + called->expect->Expect.toEqual({contents: true}) + called := false + + let _ = Js.Global.setTimeout(() => called := true, 1000) + Vi.getTimerCount()->expect->Expect.toBe(1) + let _ = await Vi.runOnlyPendingTimersAsync() + called->expect->Expect.toEqual({contents: true}) + called := false + + let _ = Js.Global.setTimeout(() => called := true, 1000) + let _ = Js.Global.setTimeout(() => called2 := true, 2000) + Vi.getTimerCount()->expect->Expect.toBe(2) + let _ = Vi.advanceTimersToNextTimer() + called->expect->Expect.toEqual({contents: true}) + called2.contents->expect->Expect.toBe(false) + + let _ = await Vi.advanceTimersToNextTimerAsync() + called2.contents->expect->Expect.toBe(true) + + let _ = Js.Global.setTimeout(() => called := true, 1000) + Vi.getTimerCount()->expect->Expect.toBe(1) + let _ = Vi.clearAllTimers() + Vi.getTimerCount()->expect->Expect.toBe(0) + + nextTick(() => called := true) + let _ = Vi.runAllTicks() + called->expect->Expect.toEqual({contents: true}) + called := false + }) + + itAsync("should compile waitFor correctly", async _t => { + let called = ref(false) + let _ = Js.Global.setTimeout(() => called := true, 100) + await Vi.waitFor(() => Assert.assert_(called.contents == true), ()) + called->expect->Expect.toEqual({contents: true}) + + let called = ref(false) + let _ = Js.Global.setTimeout(() => called := true, 100) + await Vi.waitFor(() => Assert.assert_(called.contents == true), ~timeout=200, ()) + called->expect->Expect.toEqual({contents: true}) + + let called = ref(false) + let _ = Js.Global.setTimeout(() => called := true, 100) + await Vi.waitFor(() => Assert.assert_(called.contents == true), ~interval=50, ()) + called->expect->Expect.toEqual({contents: true}) + + let called = ref(false) + let _ = Js.Global.setTimeout(() => called := true, 100) + await Vi.waitFor(() => Assert.assert_(called.contents == true), ~timeout=200, ~interval=50, ()) + called->expect->Expect.toEqual({contents: true}) + + let run = async () => { + let called = ref(false) + let _ = Js.Global.setTimeout(() => called := true, 100) + await Vi.waitFor(() => Assert.assert_(called.contents == true), ~timeout=50, ()) + called->expect->Expect.toEqual({contents: false}) + } + + await run() + ->expect + ->Expect.Promise.rejects + ->Expect.Promise.toThrow + }) + + itAsync("should compile waitForAsync correctly", async _t => { + let _ = Vi.useFakeTimers() + + let sleep = ms => { + Js.Promise2.make( + (~resolve, ~reject as _) => { + let _ = Js.Global.setTimeout(() => resolve(), ms) + }, + ) + } + + await Vi.waitForAsync(() => sleep(1), ()) + await Vi.waitForAsync(() => sleep(20), ~timeout=100, ()) + await Vi.waitForAsync(() => sleep(50), ~interval=50, ()) + await Vi.waitForAsync(() => sleep(150), ~timeout=200, ~interval=50, ()) + + let run = () => Vi.waitForAsync(() => sleep(100), ~timeout=50, ()) + + await run() + ->expect + ->Expect.Promise.rejects + ->Expect.Promise.toThrow + }) + + it("compile mocking system time correctly", _t => { + Vi.getMockedSystemTime()->expect->Expect.toBeNone + + let date = Js.Date.makeWithYMD(~year=2021., ~month=1., ~date=1., ()) + let _ = Vi.setSystemTime(#Date(date)) + // FIXME: Expect.toBeSome is not working + /* + AssertionError: expected 2021-02-01T05:00:00.000Z to not deeply equal 2021-02-01T05:00:00.000Z + ❯ Object.toBeSome src/Vitest.mjs:671:21 + 669| expected.not.toBeUndefined(); + 670| if (some !== undefined) { + 671| return expected.toEqual(Caml_option.valFromOption(some)); + | ^ + 672| } + */ + Vi.getMockedSystemTime()->Belt.Option.getExn->expect->Expect.toStrictEqual(date) + Vi.getRealSystemTime()->expect->Expect.Float.toBeGreaterThan(Js.Date.getTime(date)) + + Vi.getRealSystemTime()->expect->Expect.Float.toBeGreaterThanOrEqual(0.0) + let _ = Vi.useRealTimers() + Vi.isFakeTimers()->expect->Expect.toBe(false) + Vi.getMockedSystemTime()->expect->Expect.toBeNone + }) +})