Skip to content

Commit

Permalink
feat: add better error reporting/tests/bug fixes (#55)
Browse files Browse the repository at this point in the history
- Prints marble comparison and detailed JSON.stringified test messages below
- If notification is found in the expected TestObservable, then it will match marble character to this Notification, if not, the character will be equal to ?
- fixes not.toBeObservable.

Closes #51, #11
  • Loading branch information
krzysztof-grzybek committed Jul 18, 2020
1 parent 69d6cc4 commit a1fca8d
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 3 deletions.
59 changes: 56 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
TestHotObservable,
TestObservable,
} from './src/test-observables';
import { unparseMarble } from './src/marble-unparser';
import { mapSymbolsToNotifications } from './src/map-symbols-to-notifications';

export {
getTestScheduler,
Expand Down Expand Up @@ -105,7 +107,7 @@ export function addMatchers() {
return { pass: true };
},
}),
toBeObservable: () => ({
toBeObservable: (utils, equalityTester) => ({
compare: function(actual: TestObservable, fixture: TestObservable) {
const results: TestMessage[] = [];
let subscription: Subscription;
Expand Down Expand Up @@ -150,14 +152,65 @@ export function addMatchers() {
true,
);

expect(results).toEqual(expected);
if (utils.equals(results, expected)) {
return { pass: true };
}

return { pass: true };
const mapNotificationToSymbol = buildNotificationToSymbolMapper(
fixture.marbles,
expected,
utils.equals,
);
const receivedMarble = unparseMarble(results, mapNotificationToSymbol);

const message = formatMessage(
fixture.marbles,
expected,
receivedMarble,
results,
);
return { pass: false, message };
},
}),
});
}

function buildNotificationToSymbolMapper(
expectedMarbles: string,
expectedMessages: TestMessage[],
equalityFn: (a: any, b: any) => boolean,
) {
const symbolsToNotificationsMap = mapSymbolsToNotifications(
expectedMarbles,
expectedMessages,
);
return (notification: Notification<any>) => {
const mapped = Object.keys(symbolsToNotificationsMap).find(key => {
return equalityFn(symbolsToNotificationsMap[key], notification);
})!;

return mapped || '?';
};
}

function formatMessage(
expectedMarbles: string,
expectedMessages: TestMessage[],
receivedMarbles: string,
receivedMessages: TestMessage[],
) {
return `
Expected: ${expectedMarbles},
Received: ${receivedMarbles},
Expected:
${JSON.stringify(expectedMessages)}
Received:
${JSON.stringify(receivedMessages)},
`;
}

export function setupEnvironment() {
jasmine.getEnv().beforeAll(() => addMatchers());

Expand Down
8 changes: 8 additions & 0 deletions spec/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,12 @@ describe('Integration', () => {
expectObservable(provided).toBe('------(a|)', { a: val });
});
});

it('should support "not.toBeObservable"', () => {
const provided = of(1);

const expected = cold('(b|)', { b: 2 });

expect(provided).not.toBeObservable(expected);
});
});
53 changes: 53 additions & 0 deletions spec/map-symbols-to-notifications.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { TestScheduler } from 'rxjs/testing';
import { mapSymbolsToNotifications } from '../src/map-symbols-to-notifications';

describe('Map symbols to frames', () => {
it('should map single symbol', () => {
const result = encodeSymbols('a', { a: 1 });
expect(result.a.value).toEqual(1);
});

it('should map multiple symbols', () => {
const result = encodeSymbols('---a--bc-d|', { a: 1, b: 2, c: 3, d: 4 });
expect(result.a.value).toEqual(1);
expect(result.b.value).toEqual(2);
expect(result.c.value).toEqual(3);
expect(result.d.value).toEqual(4);
});

it('should support groups', () => {
const result = encodeSymbols('---(abc)--(aa)-|', {
a: 1,
b: 2,
c: 3,
d: 4,
});
expect(result.a.value).toEqual(1);
expect(result.b.value).toEqual(2);
expect(result.c.value).toEqual(3);
});

it('should support subscription point', () => {
const result = encodeSymbols('---a-^-b-|', { a: 1, b: 2 });
expect(result.a.value).toEqual(1);
expect(result.b.value).toEqual(2);
});

it('should support time progression', () => {
const result = encodeSymbols('-- 100ms -a-^-b-|', { a: 1, b: 2 });
expect(result.a.value).toEqual(1);
expect(result.b.value).toEqual(2);
});

function encodeSymbols(marbles: string, values: { [key: string]: any }) {
const expected = TestScheduler.parseMarbles(
marbles,
values,
undefined,
true,
true,
);

return mapSymbolsToNotifications(marbles, expected);
}
});
46 changes: 46 additions & 0 deletions spec/marble-unparser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TestScheduler } from 'rxjs/testing';
import { unparseMarble } from '../src/marble-unparser';

describe('Marble unparser', () => {
describe('Basic unparsing with single frame symbol', () => {
it('should unparse single frame', () => {
expectToUnparse('a', 'a');
});

it('should respect empty frames', () => {
expectToUnparse('---a-aaa--a', '---a-aaa--a');
});

it('should trim empty suffix frames', () => {
expectToUnparse('---a-aaa--a----', '---a-aaa--a');
});

it('should support errors', () => {
expectToUnparse('--a-#', '--a-#');
});

it('should support stream completion', () => {
expectToUnparse('--a-|', '--a-|');
});

it('should support time progression', () => {
expectToUnparse('- 20ms -a', '----a');
});

it('should support groups', () => {
expectToUnparse('-(aa)--a', '-(aa)--a');
});

function expectToUnparse(sourceMarble: string, expectedMarble: string) {
const testMessage = TestScheduler.parseMarbles(
sourceMarble,
{ a: 1 },
undefined,
true,
true,
);

expect(unparseMarble(testMessage, n => 'a')).toBe(expectedMarble);
}
});
});
43 changes: 43 additions & 0 deletions src/map-symbols-to-notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TestMessage } from 'rxjs/internal/testing/TestMessage';
import { Notification } from 'rxjs';

export function mapSymbolsToNotifications(
marbles: string,
messagesArg: TestMessage[],
): { [key: string]: Notification<any> } {
const messages = messagesArg.slice();
const result: { [key: string]: Notification<any> } = {};

for (let i = 0; i < marbles.length; i++) {
const symbol = marbles[i];

switch (symbol) {
case ' ':
case '-':
case '^':
case '(':
case ')':
break;
case '#':
case '|': {
messages.shift();
break;
}
default: {
if ((symbol.match(/^[0-9]$/) && i === 0) || marbles[i - 1] === ' ') {
const buffer = marbles.slice(i);
const match = buffer.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m) /);
if (match) {
i += match[0].length - 1;
}
break;
}

const message = messages.shift()!;
result[symbol] = message.notification;
}
}
}

return result;
}
62 changes: 62 additions & 0 deletions src/marble-unparser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { TestMessage } from 'rxjs/internal/testing/TestMessage';
import { Notification } from 'rxjs';

export function unparseMarble(
result: TestMessage[],
assignSymbolFn: (a: Notification<any>) => string,
): string {
const FRAME_TIME_FACTOR = 10; // need to be up to date with `TestScheduler.frameTimeFactor`
let frames = 0;
let marble = '';
let isInGroup = false;
let groupMembersAmount = 0;
let index = 0;

const isNextMessageInTheSameFrame = () => {
const nextMessage = result[index + 1];
return nextMessage && nextMessage.frame === result[index].frame;
};

result.forEach((testMessage, i) => {
index = i;

const framesDiff = testMessage.frame - frames;
const emptyFramesAmount =
framesDiff > 0 ? framesDiff / FRAME_TIME_FACTOR : 0;
marble += '-'.repeat(emptyFramesAmount);

if (isNextMessageInTheSameFrame()) {
if (!isInGroup) {
marble += '(';
}
isInGroup = true;
}

switch (testMessage.notification.kind) {
case 'N':
marble += assignSymbolFn(testMessage.notification);
break;
case 'E':
marble += '#';
break;
case 'C':
marble += '|';
break;
}

if (isInGroup) {
groupMembersAmount += 1;
}

if (!isNextMessageInTheSameFrame() && isInGroup) {
marble += ')';
isInGroup = false;
frames += (groupMembersAmount + 1) * FRAME_TIME_FACTOR;
groupMembersAmount = 0;
} else {
frames = testMessage.frame + FRAME_TIME_FACTOR;
}
});

return marble;
}

0 comments on commit a1fca8d

Please sign in to comment.