Skip to content

Commit 8959f03

Browse files
Merge pull request #80 from basics/feature/marble
Feature/marble
2 parents 3fb4fd0 + aabbbd8 commit 8959f03

21 files changed

+552
-551
lines changed

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"formulahendry.auto-close-tag",
1414
"formulahendry.auto-rename-tag",
1515
"naumovs.color-highlight",
16-
"humao.rest-client"
16+
"humao.rest-client",
17+
"techer.open-in-browser"
1718
]
1819
}

package-lock.json

Lines changed: 19 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mock/async.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { of } from 'rxjs';
2+
3+
export const mockAsync = v => of(v);
File renamed without changes.

packages/mock/response.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { vi } from 'vitest';
2+
3+
import { mockAsync } from './async';
4+
5+
export const mockResponse = () => {
6+
return vi.fn((e, url) => ({
7+
url: url,
8+
clone: () => new Response(e),
9+
json: () => mockAsync(e),
10+
text: () => mockAsync(e),
11+
blob: () => mockAsync(e),
12+
arrayBuffer: () => mockAsync(e),
13+
ok: true
14+
}));
15+
};

packages/observables/src/dom/window.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { tap } from 'rxjs';
22
import { TestScheduler } from 'rxjs/testing';
33
import { beforeEach, test, expect, describe, afterEach } from 'vitest';
44

5-
import { mockOffline, mockOnline, mockReset } from '../../../test-utils/network.js';
5+
import { mockOffline, mockOnline, mockReset } from '../../../mock/network.js';
66
import { connectionObservable } from './window.js';
77

88
// HINT: https://betterprogramming.pub/rxjs-testing-write-unit-tests-for-observables-603af959e251

packages/operators/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
},
2020
"dependencies": {
2121
"@rxjs-collection/observables": "*",
22+
"ansi-colors": "^4.1.3",
23+
"consola": "^3.2.3",
2224
"fast-equals": "5.0.1",
2325
"rxjs": "7.8.1"
2426
},

packages/operators/src/log.js

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
1-
import { Observable } from 'rxjs';
1+
import { bgGreen } from 'ansi-colors';
2+
import debug from 'debug';
3+
import { connectable, finalize, Observable, Subject } from 'rxjs';
24

3-
export const log = (active = true) => {
4-
if (active) {
5+
export const enableLog = tag => {
6+
debug.enable(tag);
7+
};
8+
9+
export const log = tag => {
10+
var logger = debug(tag);
11+
logger.log = console.log.bind(console);
12+
var error = debug(`${tag}:error`);
13+
14+
if (debug.enabled(tag)) {
515
return source => {
616
return new Observable(observer => {
7-
return source.subscribe(
8-
val => {
9-
console.log(val);
17+
return source.subscribe({
18+
next: val => {
19+
logger(val);
1020
observer.next(val);
1121
},
12-
err => {
13-
console.error(err);
22+
error: err => {
23+
error(err);
1424
observer.error(err);
1525
},
16-
() => {
17-
console.log('%ccomplete', 'color: green');
26+
complete: () => {
27+
logger(bgGreen.bold('Complete!'));
1828
observer.complete();
1929
}
20-
);
30+
});
2131
});
2232
};
2333
} else {
2434
return source => source;
2535
}
2636
};
37+
38+
export const logResult = (tag, observable) => {
39+
return new Promise(done => {
40+
connectable(
41+
observable.pipe(
42+
log(tag),
43+
finalize(() => done())
44+
),
45+
{ connector: () => new Subject() }
46+
).connect();
47+
});
48+
};
Lines changed: 66 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,96 @@
1-
import { concatAll, concatMap, delay, from, map, of, toArray } from 'rxjs';
1+
import { concatAll, delay, from, map, of } from 'rxjs';
22
import { TestScheduler } from 'rxjs/testing';
3-
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
3+
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
44

5-
import { log } from '../log';
5+
import { mockAsync } from '../../../mock/async';
6+
import { mockResponse } from '../../../mock/response';
7+
import { log, logResult } from '../log';
68
import { resolveJSON } from './response';
79

8-
describe('auto pagination - mocked', function () {
9-
const testScheduler = new TestScheduler((actual, expected) => {
10-
expect(actual).to.eql(expected);
11-
});
10+
describe('auto pagination', () => {
11+
let testScheduler;
1212

13-
beforeEach(function () {
14-
vi.doMock('./request', importOriginal => ({
15-
request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t))))
16-
}));
13+
beforeAll(() => {
14+
vi.spyOn(global, 'fetch').mockImplementation(({ v, t }) => mockAsync(v).pipe(delay(t)));
1715

18-
Object.prototype.clone = vi.fn();
19-
vi.spyOn(Object.prototype, 'clone').mockImplementation(function (e) {
20-
return { ...JSON.parse(JSON.stringify(this)) };
21-
});
16+
global.Response = mockResponse();
2217
});
2318

24-
afterEach(() => {
25-
vi.doUnmock('./request');
19+
beforeEach(() => {
20+
testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected));
2621
});
2722

28-
test('classic testing', async () => {
29-
const { autoPagination } = await import('./autoPagination');
30-
31-
const triggerVal = [
32-
{ t: 2, v: { value: 'a', next: 1 } },
33-
{ t: 5, v: { value: 'b', next: 2 } },
34-
{ t: 3, v: { value: 'c', next: 3 } },
35-
{ t: 1, v: { value: 'd', next: 4 } },
36-
{ t: 4, v: { value: 'e', next: null } }
37-
];
38-
39-
const expectedVal = triggerVal.map(({ v }) => v);
40-
41-
await new Promise((done, error) => {
42-
of(triggerVal[0])
43-
.pipe(
44-
autoPagination({
45-
resolveRoute: (conf, resp) =>
46-
((!resp || resp.next) && [triggerVal[resp?.next || 0]]) || []
47-
}),
48-
toArray()
49-
)
50-
.subscribe({
51-
next: e => expect(e).toStrictEqual(expectedVal),
52-
complete: () => done(),
53-
error: () => error()
54-
});
55-
});
23+
afterAll(() => {
24+
vi.restoreAllMocks();
5625
});
5726

58-
test('marble testing', async () => {
27+
test('default', async () => {
5928
const { autoPagination } = await import('./autoPagination');
6029

61-
const triggerVal = {
62-
a: { t: 2, v: { value: 'a', next: 'b' } },
63-
b: { t: 5, v: { value: 'b', next: 'c' } },
64-
c: { t: 3, v: { value: 'c', next: 'd' } },
65-
d: { t: 1, v: { value: 'd', next: 'e' } },
66-
e: { t: 4, v: { value: 'e', next: null } }
30+
const expectedVal = {
31+
a: { value: 'a', next: 'b' },
32+
b: { value: 'b', next: 'c' },
33+
c: { value: 'c', next: 'd' },
34+
d: { value: 'd', next: 'e' },
35+
e: { value: 'e', next: null }
6736
};
6837

69-
const expectedVal = Object.fromEntries(
70-
Array.from(Object.entries(triggerVal)).map(([k, { v }]) => [k, v])
71-
);
38+
const triggerVal = {
39+
a: { t: 2, v: new Response(expectedVal.a) },
40+
b: { t: 5, v: new Response(expectedVal.b) },
41+
c: { t: 3, v: new Response(expectedVal.c) },
42+
d: { t: 1, v: new Response(expectedVal.d) },
43+
e: { t: 4, v: new Response(expectedVal.e) }
44+
};
7245

7346
testScheduler.run(({ cold, expectObservable }) => {
7447
expectObservable(
75-
cold('-a-------------------', triggerVal).pipe(
48+
cold('-a-------------------', { a: 'a' }).pipe(
7649
autoPagination({
77-
resolveRoute: (conf, resp) =>
78-
((!resp || resp.next) && [triggerVal[resp?.next || 'a']]) || []
79-
})
50+
resolveRoute: (url, resp) => {
51+
if (resp) {
52+
return from(resp.json()).pipe(map(({ next }) => triggerVal[String(next)]));
53+
}
54+
return of(triggerVal[String(url)]);
55+
}
56+
}),
57+
resolveJSON(),
58+
log('marble:result')
8059
)
8160
).toBe('---a----b--cd---e----', expectedVal);
8261
});
8362
});
8463
});
8564

86-
describe.skip('auto pagination - demo', function () {
87-
beforeEach(function () {
88-
vi.resetModules();
89-
});
90-
91-
test('sample testing', async function () {
65+
describe('auto pagination - demo', () => {
66+
test('sample', async () => {
9267
const { autoPagination } = await import('./autoPagination');
9368

94-
return new Promise(done => {
95-
return of(new URL('https://dummyjson.com/products'))
96-
.pipe(
97-
autoPagination({
98-
resolveRoute: async (url, resp) => {
99-
const data = (await resp?.json()) || { skip: -10, limit: 10 };
100-
101-
if (!data.total || data.total > data.skip + data.limit) {
102-
const newUrl = new URL(`${url}`);
103-
newUrl.searchParams.set('skip', data.skip + data.limit);
104-
newUrl.searchParams.set('limit', data.limit);
105-
newUrl.searchParams.set('select', 'title,price');
106-
return newUrl;
107-
}
108-
}
109-
}),
110-
log(false),
111-
resolveJSON(),
112-
log(false),
113-
map(({ products }) => products),
114-
concatAll()
115-
)
116-
.subscribe({
117-
next: e => console.log(e),
118-
complete: () => done()
119-
});
120-
});
69+
await logResult(
70+
'demo',
71+
of(new URL('https://dummyjson.com/products')).pipe(
72+
autoPagination({
73+
resolveRoute: (url, resp) => {
74+
return from(resp?.json() || of({ skip: -10, limit: 10 })).pipe(
75+
map(data => {
76+
if (!data.total || data.total > data.skip + data.limit) {
77+
const newUrl = new URL(`${url}`);
78+
newUrl.searchParams.set('skip', data.skip + data.limit);
79+
newUrl.searchParams.set('limit', data.limit);
80+
newUrl.searchParams.set('select', 'title,price');
81+
return newUrl;
82+
}
83+
})
84+
);
85+
}
86+
}),
87+
log('demo:response'),
88+
resolveJSON(),
89+
log('demo:response:json'),
90+
map(({ products }) => products),
91+
log('demo:response:result'),
92+
concatAll()
93+
)
94+
);
12195
});
12296
});
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import { ReplaySubject, share, tap, timer } from 'rxjs';
1+
import { ReplaySubject, share, timer } from 'rxjs';
22

33
export const cache = ttl => {
44
return source =>
55
source.pipe(
66
share({
7-
// TODO: check if a buffer size is neccessary
87
connector: () => new ReplaySubject(),
9-
// resetOnError: false,
108
resetOnComplete: () => timer(ttl),
11-
resetOnRefCountZero: false
9+
resetOnRefCountZero: () => timer(ttl)
1210
})
1311
);
1412
};

0 commit comments

Comments
 (0)