Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add more test coverage #333

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading