Skip to content

Commit

Permalink
chore: add more test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Jan 9, 2025
1 parent 88ab5e5 commit 24fb681
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 97 deletions.
194 changes: 119 additions & 75 deletions test/actor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ import {dummyLogic} from './fixture.js';
describe('xstate-audition', () => {
describe('actor', () => {
describe('patchActor()', () => {
describe('when "actor" is not an ActorRef', () => {
it('should throw', () => {
const notAnActor = {};

assert.throws(
() => {
// @ts-expect-error bad type
patchActor(notAnActor);
},
{
message: 'patchActor() called with a non-ActorRef',
name: 'TypeError',
},
);
});
});
describe('when no inspector provided', () => {
it('should not add an inspector to the actor', () => {
const actor = createActor(dummyLogic);
Expand Down Expand Up @@ -148,98 +164,126 @@ describe('xstate-audition', () => {

let inspector: Mock<(evt: InspectionEvent) => void>;

beforeEach(() => {
actor = createActor(dummyLogic);

originalLogger =
// @ts-expect-error private
actor.logger =
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
actor._actorScope.logger =
actor.system._logger =
mock.fn();

newLogger = mock.fn<LoggerFn>();
inspector = mock.fn<(evt: InspectionEvent) => void>();

patchActor(actor, {inspector, logger: newLogger});
});

describe('when patched once', () => {
it('should restore the original logger', () => {
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');
assert.equal(
originalLogger.mock.callCount(),
0,
'original logger was called',
);
assert.equal(
newLogger.mock.callCount(),
1,
'new logger was not called once',
);
describe('when actor was previously patched', () => {
beforeEach(() => {
actor = createActor(dummyLogic);

unpatchActor(actor);
originalLogger =
// @ts-expect-error private
actor.logger =
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
actor._actorScope.logger =
actor.system._logger =
mock.fn();

// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');
newLogger = mock.fn<LoggerFn>();
inspector = mock.fn<(evt: InspectionEvent) => void>();

assert.equal(
originalLogger.mock.callCount(),
1,
'original logger was not called once',
);
patchActor(actor, {inspector, logger: newLogger});
});
describe('when patched once', () => {
it('should restore the original logger', () => {
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');
assert.equal(
originalLogger.mock.callCount(),
0,
'original logger was called',
);
assert.equal(
newLogger.mock.callCount(),
1,
'new logger was not called once',
);

unpatchActor(actor);

// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');

assert.equal(
originalLogger.mock.callCount(),
1,
'original logger was not called once',
);
});

it('should unsubscribe the inspector', () => {
actor.start();
actor.stop();
assert.equal(inspector.mock.callCount(), 3);

it('should unsubscribe the inspector', () => {
actor.start();
actor.stop();
assert.equal(inspector.mock.callCount(), 3);

unpatchActor(actor);
actor.start();
unpatchActor(actor);
actor.start();

try {
assert.equal(inspector.mock.callCount(), 3);
} finally {
actor.stop();
}
try {
assert.equal(inspector.mock.callCount(), 3);
} finally {
actor.stop();
}
});
});
describe('when patched multiple times', () => {
it('should restore the previous logger', () => {
const evenNewerLogger = mock.fn<LoggerFn>();

patchActor(actor, {logger: evenNewerLogger});
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');
assert.equal(
newLogger.mock.callCount(),
0,
'new logger was called before unpatching',
);
assert.equal(
evenNewerLogger.mock.callCount(),
1,
'even newer logger was not called once before unpatching',
);

unpatchActor(actor);
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');

assert.equal(
newLogger.mock.callCount(),
1,
'new logger was not called once after unpatching',
);
});
});

it.todo('should inherit parent logger');
});

describe('when patched multiple times', () => {
it('should restore the previous logger', () => {
const evenNewerLogger = mock.fn<LoggerFn>();
describe('when actor was not previously patched', () => {
beforeEach(() => {
actor = createActor(dummyLogic);

originalLogger =
// @ts-expect-error private
actor.logger =
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
actor._actorScope.logger =
actor.system._logger =
mock.fn();
});

patchActor(actor, {logger: evenNewerLogger});
it('should not mutate the actor', () => {
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');
assert.equal(
newLogger.mock.callCount(),
0,
'new logger was called before unpatching',
);
assert.equal(
evenNewerLogger.mock.callCount(),
1,
'even newer logger was not called once before unpatching',
);

unpatchActor(actor);
// @ts-expect-error private
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
actor.logger('test');

assert.equal(
newLogger.mock.callCount(),
1,
'new logger was not called once after unpatching',
);
assert.equal(originalLogger.mock.callCount(), 2);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion test/example-spawn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const spawnerMachine = setup({

describe('spawnerMachine', () => {
it('should spawn a child with ID "noopPromise" when "SPAWN" event received', async () => {
const actor = createActor(spawnerMachine);
const actor = createActor(spawnerMachine, {logger: () => {}});

try {
// spawnerMachine needs an event to spawn the actor. but at this point,
Expand Down
27 changes: 16 additions & 11 deletions test/harness.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import {strict as assert} from 'node:assert';
import {afterEach, beforeEach, describe, it} from 'node:test';
import {isPromise} from 'node:util/types';
import {
type Actor,
type AnyActorLogic,
createActor,
type Snapshot,
} from 'xstate';

import {type AnyActor} from '../src/types.js';
import {type Actor, type AnyActorLogic, type Snapshot} from 'xstate';

import {createActorWith} from '../src/create-actor.js';
import {type AnyActor, type AuditionOptions} from '../src/types.js';
import {noop} from '../src/util.js';

export type TestAuditionOptions<Actor extends AnyActor = AnyActor> = {
export interface TestAuditionOptions<Actor extends AnyActor = AnyActor>
extends Omit<AuditionOptions, 'timeout'> {
resolver?: (actor: Actor) => void;
shouldStop?: boolean;
};
}

/**
* Tests a curried function by verifying its behavior when called with partial
Expand All @@ -32,6 +29,7 @@ export type TestAuditionOptions<Actor extends AnyActor = AnyActor> = {
* @param args - The arguments to be passed to the source function.
* @param resolver - A function which will trigger the final `Promise` to
* resolve, if needed
* @param options - Additional options to configure the test.
* @throws Will throw an error if the number of provided arguments does not
* match the arity of the source function.
* @throws Will throw an error if the source function does not have at least one
Expand All @@ -46,7 +44,12 @@ export function testCurried<
expectationFn: (actual: TReturn) => void,
logic: Logic,
args?: unknown[],
{resolver = noop, shouldStop = false}: TestAuditionOptions = {},
{
inspector: inspect = noop,
logger = noop,
resolver = noop,
shouldStop = false,
}: TestAuditionOptions = {},
) {
const arity = sourceFn.length;

Expand All @@ -59,6 +62,8 @@ export function testCurried<
assert.ok(arity > 0, 'source function must have at least one argument');
assert.ok(logic, 'must provide logic to create actor');

const createActor = createActorWith<Logic>({inspect, logger});

describe('common behavior', () => {
let actor: Actor<Logic>;

Expand Down
6 changes: 3 additions & 3 deletions test/until-emitted.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('xstate-audition', () => {
});

it('should satisfy the expected type', () => {
runUntilEmitted satisfies CurryEmitted;
expectTypeOf(runUntilEmitted).toMatchTypeOf<CurryEmitted>();
});

describe('when called with all arguments', () => {
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('xstate-audition', () => {
});

it('should satisfy the expected type', () => {
waitForEmitted satisfies CurryEmitted;
expectTypeOf(waitForEmitted).toMatchTypeOf<CurryEmitted>();
});

describe('when called with all arguments', () => {
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('xstate-audition', () => {
});

it('should satisfy the expected type', () => {
runUntilEmittedWith satisfies CurryEmittedWith;
expectTypeOf(runUntilEmittedWith).toMatchTypeOf<CurryEmittedWith>();
});

describe('when called with all arguments', () => {
Expand Down
7 changes: 6 additions & 1 deletion test/until-event-received.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {expectTypeOf} from 'expect-type';
import {strict as assert} from 'node:assert';
import {describe, it} from 'node:test';
import {type Actor, createActor} from 'xstate';
import {type Actor} from 'xstate';

import {
createActorWith,
runUntilEventReceived,
runUntilEventReceivedWith,
} from '../src/index.js';
Expand All @@ -12,6 +13,10 @@ import {testCurried} from './harness.js';

describe('xstate-audition', () => {
describe('until-event-received', () => {
const createActor = createActorWith<typeof receiverMachine>({
logger: () => {},
});

describe('runUntilEventReceived()', () => {
testCurried(
runUntilEventReceived,
Expand Down
7 changes: 6 additions & 1 deletion test/until-event-sent.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {expectTypeOf} from 'expect-type';
import {strict as assert} from 'node:assert';
import {beforeEach, describe, it} from 'node:test';
import {type Actor, createActor} from 'xstate';
import {type Actor} from 'xstate';

import {
createActorWith,
runUntilEventSent,
runUntilEventSentWith,
waitForEventSent,
Expand All @@ -18,6 +19,10 @@ import {testCurried} from './harness.js';

describe('xstate-audition', () => {
describe('until-event-sent', () => {
const createActor = createActorWith<typeof senderMachine>({
logger: () => {},
});

describe('runUntilEventSent()', () => {
let actor: Actor<typeof senderMachine>;

Expand Down
7 changes: 6 additions & 1 deletion test/until-spawn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {expectTypeOf} from 'expect-type';
import {strict as assert} from 'node:assert';
import {afterEach, beforeEach, describe, it} from 'node:test';
import {type Actor, type AnyActorRef, createActor} from 'xstate';
import {type Actor, type AnyActorRef} from 'xstate';

import {createActorWith} from '../src/create-actor.js';
import {
runUntilSpawn,
runUntilSpawnWith,
Expand All @@ -14,6 +15,10 @@ import {testCurried} from './harness.js';

describe('xstate-audition', () => {
describe('until-spawn', () => {
const createActor = createActorWith<typeof spawnerMachine>({
logger: () => {},
});

describe('runUntilSpawn()', () => {
testCurried<AnyActorRef>(
runUntilSpawn,
Expand Down
Loading

0 comments on commit 24fb681

Please sign in to comment.