From 4ab3e8164d2fb347191b35e87c3ac33a2d1922f5 Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Mon, 28 Oct 2024 17:44:41 +0100 Subject: [PATCH 01/17] fix(fetch): additional stuff --- .../fixtures/paginatedFetchFixture.js | 12 +++++ .../src/fetch/concurrentDownload.test.js | 40 -------------- packages/operators/src/json.js | 35 ++++++++++++ packages/operators/src/json/replacer.js | 10 ++++ packages/operators/src/json/reviver.js | 26 +++++++++ .../operators/src/json/reworker/default.js | 30 +++++++++++ packages/operators/src/json/reworker/token.js | 43 +++++++++++++++ .../src/{fetch => request}/autoPagination.js | 0 .../{fetch => request}/autoPagination.test.js | 0 packages/operators/src/request/cache.js | 12 +++++ packages/operators/src/request/cache.test.js | 52 ++++++++++++++++++ .../concurrentRequest.js} | 4 +- .../src/request/concurrentRequest.sample.js | 31 +++++++++++ .../src/request/concurrentRequest.test.js | 54 +++++++++++++++++++ .../src/{fetch => request}/lazyPagination.js | 4 +- .../{fetch => request}/lazyPagination.test.js | 0 .../src/{fetch => request}/polling.js | 0 .../src/{fetch => request}/polling.test.js | 0 .../src/{fetch => request}/request.http | 0 .../src/{fetch => request}/request.js | 1 + .../src/{fetch => request}/request.test.js | 0 .../src/{fetch => request}/response.js | 0 .../src/{fetch => request}/response.test.js | 0 .../operators/src/{fetch => request}/retry.js | 0 .../src/{fetch => request}/retry.test.js | 0 25 files changed, 310 insertions(+), 44 deletions(-) create mode 100644 packages/operators/fixtures/paginatedFetchFixture.js delete mode 100644 packages/operators/src/fetch/concurrentDownload.test.js create mode 100644 packages/operators/src/json.js create mode 100644 packages/operators/src/json/replacer.js create mode 100644 packages/operators/src/json/reviver.js create mode 100644 packages/operators/src/json/reworker/default.js create mode 100644 packages/operators/src/json/reworker/token.js rename packages/operators/src/{fetch => request}/autoPagination.js (100%) rename packages/operators/src/{fetch => request}/autoPagination.test.js (100%) create mode 100644 packages/operators/src/request/cache.js create mode 100644 packages/operators/src/request/cache.test.js rename packages/operators/src/{fetch/concurrentDownload.js => request/concurrentRequest.js} (56%) create mode 100644 packages/operators/src/request/concurrentRequest.sample.js create mode 100644 packages/operators/src/request/concurrentRequest.test.js rename packages/operators/src/{fetch => request}/lazyPagination.js (74%) rename packages/operators/src/{fetch => request}/lazyPagination.test.js (100%) rename packages/operators/src/{fetch => request}/polling.js (100%) rename packages/operators/src/{fetch => request}/polling.test.js (100%) rename packages/operators/src/{fetch => request}/request.http (100%) rename packages/operators/src/{fetch => request}/request.js (94%) rename packages/operators/src/{fetch => request}/request.test.js (100%) rename packages/operators/src/{fetch => request}/response.js (100%) rename packages/operators/src/{fetch => request}/response.test.js (100%) rename packages/operators/src/{fetch => request}/retry.js (100%) rename packages/operators/src/{fetch => request}/retry.test.js (100%) diff --git a/packages/operators/fixtures/paginatedFetchFixture.js b/packages/operators/fixtures/paginatedFetchFixture.js new file mode 100644 index 0000000..58c2c81 --- /dev/null +++ b/packages/operators/fixtures/paginatedFetchFixture.js @@ -0,0 +1,12 @@ +const test = await fetch('https://dummyjson.com/products?limit=10&skip=0&select=title,price'); +console.log(test); +const a = new Blob([test]); +console.log(a); + +const fr = new FileReader(); + +fr.onload = function () { + console.log(JSON.parse(this.result)); +}; + +fr.readAsText(a); diff --git a/packages/operators/src/fetch/concurrentDownload.test.js b/packages/operators/src/fetch/concurrentDownload.test.js deleted file mode 100644 index 287f6d0..0000000 --- a/packages/operators/src/fetch/concurrentDownload.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import { concatAll, map, of } from 'rxjs'; -import { beforeEach, describe, expect, test } from 'vitest'; - -import { log } from '../log'; -import { concurrentDownload } from './concurrentDownload'; -import { resolveJSON } from './response'; - -describe('multi fetch', function () { - beforeEach(function () { - // - }); - - test('request pagination', async function () { - return new Promise(done => { - of( - new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=20&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=30&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=40&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=50&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') - ) - .pipe( - concurrentDownload(4), - log(false), - resolveJSON(), - log(false), - map(({ products }) => products), - concatAll() - ) - .subscribe({ - next: e => console.log(e), - complete: () => done() - }); - }); - }); -}); diff --git a/packages/operators/src/json.js b/packages/operators/src/json.js new file mode 100644 index 0000000..c1c9a72 --- /dev/null +++ b/packages/operators/src/json.js @@ -0,0 +1,35 @@ +import { concatMap, map } from 'rxjs'; + +import { replaceTypes } from './json/replacer.js'; +import { reviveTypes } from './json/reviver.js'; +import { defaultDeserializeReworker, defaultSerializeReworker } from './json/reworker/default.js'; +import { signJSON, verifyJSON } from './sign.js'; + +export const serialize = (reworker, signer) => source => + source.pipe( + rework(reworker || defaultSerializeReworker), + map(data => JSON.stringify(data, replaceTypes)), + signJSON(signer) + ); + +export const deserialize = (reworker, signAddress) => source => + source.pipe( + verifyJSON(signAddress), + map(data => JSON.parse(data, reviveTypes)), + rework(reworker || defaultDeserializeReworker) + ); + +const rework = reworker => source => + source.pipe( + concatMap(data => reworkEntry('data', data, reworker)), + map(data => Object.fromEntries(data).data) + ); + +const reworkEntry = async (key, value, reworker) => { + const { transform } = reworker.find(({ type }) => type === value?.constructor) || {}; + if (transform) { + return await transform(key, value, reworkEntry, reworker); + } else { + return [[key, value]]; + } +}; diff --git a/packages/operators/src/json/replacer.js b/packages/operators/src/json/replacer.js new file mode 100644 index 0000000..2918342 --- /dev/null +++ b/packages/operators/src/json/replacer.js @@ -0,0 +1,10 @@ +export const replaceTypes = (key, value) => { + if (value?.constructor && value.constructor === Date) { + return value.toISOString(); + } + + if (value?.constructor && value.constructor === BigInt) { + return value.toString(); + } + return value; +}; diff --git a/packages/operators/src/json/reviver.js b/packages/operators/src/json/reviver.js new file mode 100644 index 0000000..63b618d --- /dev/null +++ b/packages/operators/src/json/reviver.js @@ -0,0 +1,26 @@ +export const reviveTypes = (key, value) => { + if (isValidUrl(value)) { + return new URL(value); + } + if (isValidISODateString(value)) { + return new Date(value); + } + if (isBigInt(value)) { + return BigInt(value); + } + return value; +}; + +const isValidUrl = value => { + return URL.canParse(value) && /^[\w]+:\/\/\S+$/gm.test(value); +}; + +function isValidISODateString(value) { + if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(value)) return false; + const d = new Date(value); + return d instanceof Date && !isNaN(d.getTime()) && d.toISOString() === value; // valid date +} + +function isBigInt(value) { + return value?.constructor === String && /^\d+$/.test(value); +} diff --git a/packages/operators/src/json/reworker/default.js b/packages/operators/src/json/reworker/default.js new file mode 100644 index 0000000..42a0c4a --- /dev/null +++ b/packages/operators/src/json/reworker/default.js @@ -0,0 +1,30 @@ +export const defaultReworker = [ + { + type: Array, + transform: async (key, value, rework, reworker) => [ + [ + key, + Array.from( + await Promise.all(value.map((entry, key) => rework(key, entry, reworker))), + ([[, value]]) => value + ) + ] + ] + }, + { + type: Object, + transform: async (key, value, rework, reworker) => [ + [ + key, + Object.fromEntries( + await Object.entries(value).reduce(async (accumulator, [key, value]) => { + return [...(await accumulator), ...(await rework(key, await value, reworker))]; + }, Promise.resolve([])) + ) + ] + ] + } +]; + +export const defaultSerializeReworker = defaultReworker; +export const defaultDeserializeReworker = defaultReworker; diff --git a/packages/operators/src/json/reworker/token.js b/packages/operators/src/json/reworker/token.js new file mode 100644 index 0000000..16a3f3b --- /dev/null +++ b/packages/operators/src/json/reworker/token.js @@ -0,0 +1,43 @@ +import { defaultDeserializeReworker, defaultSerializeReworker } from './default.js'; + +const serializeMapping = { + tokenData: { name: 'tokenURI', preserve: false }, + contractData: { name: 'contractURI', preserve: false } +}; +export const serializeReworker = resolve => + defaultSerializeReworker.concat([ + { + type: Blob, + transform: async (key, value) => transform(key, value, serializeMapping[String(key)], resolve) + } + ]); + +const deserializeMapping = { + tokenURI: { name: 'tokenData', preserve: true }, + contractURI: { name: 'contractData', preserve: true } +}; +export const deserializeReworker = resolve => + defaultDeserializeReworker.concat([ + { + type: URL, + transform: async (key, value) => + transform(key, value, deserializeMapping[String(key)], resolve) + } + ]); + +const transform = async (key, value, config, resolve) => { + const result = await resolve(normalizeUrl(value), key); + if (config?.preserve) { + result.tokenURI = value.toString(); + } + return [ + !config || config.preserve ? [key, value] : undefined, + [config?.name || key, result] + ].filter(Boolean); +}; + +const normalizeUrl = url => { + const regex = /^(?\w+:)\/\/(?\w+)\/*(?[\w.]+)*$/gm; + const { groups } = regex.exec(url); + return url + (groups.filename ? '' : '0'); +}; diff --git a/packages/operators/src/fetch/autoPagination.js b/packages/operators/src/request/autoPagination.js similarity index 100% rename from packages/operators/src/fetch/autoPagination.js rename to packages/operators/src/request/autoPagination.js diff --git a/packages/operators/src/fetch/autoPagination.test.js b/packages/operators/src/request/autoPagination.test.js similarity index 100% rename from packages/operators/src/fetch/autoPagination.test.js rename to packages/operators/src/request/autoPagination.test.js diff --git a/packages/operators/src/request/cache.js b/packages/operators/src/request/cache.js new file mode 100644 index 0000000..a0f6e01 --- /dev/null +++ b/packages/operators/src/request/cache.js @@ -0,0 +1,12 @@ +import { ReplaySubject, share, timer } from 'rxjs'; + +export const cache = ttl => { + return source => + source.pipe( + share({ + // TODO: check if a buffer size is neccessary + connector: () => new ReplaySubject(), + resetOnComplete: () => timer(ttl) + }) + ); +}; diff --git a/packages/operators/src/request/cache.test.js b/packages/operators/src/request/cache.test.js new file mode 100644 index 0000000..8d7a436 --- /dev/null +++ b/packages/operators/src/request/cache.test.js @@ -0,0 +1,52 @@ +import fetchMock from 'fetch-mock'; +import { defer, of, tap } from 'rxjs'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; + +import { cache } from './cache'; +import { requestText } from './request'; + +describe('cache', function () { + beforeEach(function () { + let counter = 0; + fetchMock.mockGlobal().get( + 'https://httpbin.org/my-url-fast', + () => { + return new Response(++counter, { + status: 200, + headers: { 'Content-type': 'plain/text' } + }); + }, + { delay: 0, repeat: 2 } + ); + }); + + afterEach(function () { + fetchMock.unmockGlobal(); + }); + + test('cache resetted after 100ms', async function () { + const a = of('https://httpbin.org/my-url-fast').pipe(requestText(), cache(1000)); + await new Promise(done => { + a.subscribe({ + next: e => expect(e).toBe('1'), + complete: () => done() + }); + }); + + await new Promise(done => { + a.subscribe({ + next: e => expect(e).toBe('1'), + complete: () => done() + }); + }); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + await new Promise(done => { + a.subscribe({ + next: e => expect(e).toBe('2'), + complete: () => done() + }); + }); + }); +}); diff --git a/packages/operators/src/fetch/concurrentDownload.js b/packages/operators/src/request/concurrentRequest.js similarity index 56% rename from packages/operators/src/fetch/concurrentDownload.js rename to packages/operators/src/request/concurrentRequest.js index c129913..3dc770d 100644 --- a/packages/operators/src/fetch/concurrentDownload.js +++ b/packages/operators/src/request/concurrentRequest.js @@ -1,7 +1,7 @@ -import { mergeMap, of } from 'rxjs'; +import { mergeMap, of, tap } from 'rxjs'; import { request } from './request'; -export const concurrentDownload = (concurrent = 1) => { +export const concurrentRequest = (concurrent = 1) => { return source => source.pipe(mergeMap(url => of(url).pipe(request()), concurrent)); }; diff --git a/packages/operators/src/request/concurrentRequest.sample.js b/packages/operators/src/request/concurrentRequest.sample.js new file mode 100644 index 0000000..696d315 --- /dev/null +++ b/packages/operators/src/request/concurrentRequest.sample.js @@ -0,0 +1,31 @@ +import { concatAll, map, of } from 'rxjs'; + +import { log } from '../log'; +import { concurrentRequest } from './concurrentRequest'; +import { resolveJSON } from './response'; + +await new Promise(done => { + of( + new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=20&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=30&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=40&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=50&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') + ) + .pipe( + concurrentRequest(4), + log(false), + resolveJSON(), + log(false), + map(({ products }) => products), + concatAll() + ) + .subscribe({ + next: e => console.log(e), + complete: () => done() + }); +}); diff --git a/packages/operators/src/request/concurrentRequest.test.js b/packages/operators/src/request/concurrentRequest.test.js new file mode 100644 index 0000000..208381a --- /dev/null +++ b/packages/operators/src/request/concurrentRequest.test.js @@ -0,0 +1,54 @@ +import { concatMap, delay, of, tap, toArray } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { concurrentRequest } from './concurrentRequest'; + +describe('multi fetch33', function () { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).to.eql(expected); + }); + + beforeEach(function () { + vi.mock('./request.js', async importOriginal => { + return { + request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) + }; + }); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + test('test', async () => { + const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(v => ({ v, t: Math.random() * 1000 })); + const sortedResult = [...values].sort((a, b) => a.t - b.t).map(({ v }) => v); + + await new Promise(done => { + of(...values) + .pipe(concurrentRequest(values.length), toArray()) + .subscribe({ next: e => expect(e).to.eql(sortedResult), complete: () => done() }); + }); + }); + + test('test2', async () => { + const triggerValues = { + a: { t: 2, v: 'a' }, + b: { t: 5, v: 'b' }, + c: { t: 0, v: 'c' }, + d: { t: 1, v: 'd' }, + e: { t: 1, v: 'd' } + }; + const expectedValues = Object.fromEntries( + Array.from(Object.entries(triggerValues)).map(([k, { v }]) => [k, v]) + ); + + testScheduler.run(({ cold, expectObservable }) => { + expectObservable(cold('-a-b-c-(de)', triggerValues).pipe(concurrentRequest(3), tap())).toBe( + '---a-c--(bde)', + expectedValues + ); + }); + }); +}); diff --git a/packages/operators/src/fetch/lazyPagination.js b/packages/operators/src/request/lazyPagination.js similarity index 74% rename from packages/operators/src/fetch/lazyPagination.js rename to packages/operators/src/request/lazyPagination.js index 15115ea..3987ebc 100644 --- a/packages/operators/src/fetch/lazyPagination.js +++ b/packages/operators/src/request/lazyPagination.js @@ -1,6 +1,6 @@ import { concatMap, map } from 'rxjs'; -import { concurrentDownload } from './concurrentDownload'; +import { concurrentRequest } from './concurrentRequest'; export const lazyPagination = ({ resolveRoute }) => { return source => @@ -8,7 +8,7 @@ export const lazyPagination = ({ resolveRoute }) => { concatMap(({ url, pager, concurrent }) => { return pager.pipe( map(options => resolveRoute(url, options)), - concurrentDownload(concurrent) + concurrentRequest(concurrent) ); }) ); diff --git a/packages/operators/src/fetch/lazyPagination.test.js b/packages/operators/src/request/lazyPagination.test.js similarity index 100% rename from packages/operators/src/fetch/lazyPagination.test.js rename to packages/operators/src/request/lazyPagination.test.js diff --git a/packages/operators/src/fetch/polling.js b/packages/operators/src/request/polling.js similarity index 100% rename from packages/operators/src/fetch/polling.js rename to packages/operators/src/request/polling.js diff --git a/packages/operators/src/fetch/polling.test.js b/packages/operators/src/request/polling.test.js similarity index 100% rename from packages/operators/src/fetch/polling.test.js rename to packages/operators/src/request/polling.test.js diff --git a/packages/operators/src/fetch/request.http b/packages/operators/src/request/request.http similarity index 100% rename from packages/operators/src/fetch/request.http rename to packages/operators/src/request/request.http diff --git a/packages/operators/src/fetch/request.js b/packages/operators/src/request/request.js similarity index 94% rename from packages/operators/src/fetch/request.js rename to packages/operators/src/request/request.js index 8191a27..ef7357f 100644 --- a/packages/operators/src/fetch/request.js +++ b/packages/operators/src/request/request.js @@ -1,5 +1,6 @@ import { concatMap } from 'rxjs'; +import { cache } from './cache'; import { resolveBlob, resolveJSON, resolveText } from './response'; import { networkRetry } from './retry'; diff --git a/packages/operators/src/fetch/request.test.js b/packages/operators/src/request/request.test.js similarity index 100% rename from packages/operators/src/fetch/request.test.js rename to packages/operators/src/request/request.test.js diff --git a/packages/operators/src/fetch/response.js b/packages/operators/src/request/response.js similarity index 100% rename from packages/operators/src/fetch/response.js rename to packages/operators/src/request/response.js diff --git a/packages/operators/src/fetch/response.test.js b/packages/operators/src/request/response.test.js similarity index 100% rename from packages/operators/src/fetch/response.test.js rename to packages/operators/src/request/response.test.js diff --git a/packages/operators/src/fetch/retry.js b/packages/operators/src/request/retry.js similarity index 100% rename from packages/operators/src/fetch/retry.js rename to packages/operators/src/request/retry.js diff --git a/packages/operators/src/fetch/retry.test.js b/packages/operators/src/request/retry.test.js similarity index 100% rename from packages/operators/src/fetch/retry.test.js rename to packages/operators/src/request/retry.test.js From 9ef18c497f67c8ee524373fdd3e627c168a297f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:22:28 +0000 Subject: [PATCH 02/17] chore(deps): update all non-major dependencies --- package-lock.json | 341 ++++++++++++++++++++++++---------------------- package.json | 14 +- 2 files changed, 184 insertions(+), 171 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e6c484..aebbdb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,26 +17,26 @@ "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", - "@semantic-release/github": "11.0.0", + "@semantic-release/github": "11.0.1", "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.1", - "@vitest/coverage-v8": "2.1.3", + "@vitest/coverage-v8": "2.1.5", "commitlint": "19.5.0", - "eslint": "9.13.0", + "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-no-secrets": "1.0.2", + "eslint-plugin-no-secrets": "1.1.2", "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", - "fetch-mock": "12.0.0", + "fetch-mock": "12.1.0", "happy-dom": "15.7.4", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", - "semantic-release": "24.1.3", + "semantic-release": "24.2.0", "semantic-release-monorepo": "8.0.2", - "vitest": "2.1.3" + "vitest": "2.1.5" } }, "node_modules/@ampproject/remapping": { @@ -936,9 +936,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { @@ -1019,9 +1019,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", "dev": true, "license": "MIT", "engines": { @@ -1052,9 +1052,9 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", - "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1062,13 +1062,13 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", - "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.0", + "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" }, "engines": { @@ -1817,9 +1817,9 @@ } }, "node_modules/@semantic-release/github": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.0.tgz", - "integrity": "sha512-Uon6G6gJD8U1JNvPm7X0j46yxNRJ8Ui6SgK4Zw5Ktu8RgjEft3BGn+l/RX1TTzhhO3/uUcKuqM+/9/ETFxWS/Q==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", + "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", "dev": true, "license": "MIT", "dependencies": { @@ -2544,22 +2544,22 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", - "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", + "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", - "std-env": "^3.7.0", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -2567,8 +2567,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.3", - "vitest": "2.1.3" + "@vitest/browser": "2.1.5", + "vitest": "2.1.5" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2577,15 +2577,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", - "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", + "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2593,22 +2593,21 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", - "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", + "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.3", + "@vitest/spy": "2.1.5", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.3", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -2621,9 +2620,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", - "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", "dev": true, "license": "MIT", "dependencies": { @@ -2634,13 +2633,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", - "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", + "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.3", + "@vitest/utils": "2.1.5", "pathe": "^1.1.2" }, "funding": { @@ -2648,14 +2647,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", - "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", + "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.5", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -2663,27 +2662,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", - "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", + "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", "dev": true, "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", - "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", + "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.3", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.5", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -2691,9 +2690,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -2919,9 +2918,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { @@ -4082,6 +4081,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -4145,22 +4151,22 @@ } }, "node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", + "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", + "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.18.0", "@eslint/core": "^0.7.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", + "@eslint/js": "9.14.0", "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", + "@humanwhocodes/retry": "^0.4.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -4168,9 +4174,9 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4219,9 +4225,9 @@ } }, "node_modules/eslint-plugin-no-secrets": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-no-secrets/-/eslint-plugin-no-secrets-1.0.2.tgz", - "integrity": "sha512-lXjGcPS6ZMxAouYWsuX5NGsLlOWQ5c+YFHHZFECzRCZIssYQgWVPINgZqAU7caquB32MoEAL+dXRQNDBX0fgwQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-secrets/-/eslint-plugin-no-secrets-1.1.2.tgz", + "integrity": "sha512-FjgyBaEkQK6hrdKf0V1TnKbY3dxXmw8S7tjfHs/BMIgFGNYhzFccxbZSJtDCPHTQTSiBtdLwRlOmSF81toII4w==", "dev": true, "license": "MIT", "engines": { @@ -4405,9 +4411,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4422,9 +4428,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4434,12 +4440,19 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", @@ -4607,15 +4620,15 @@ } }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4724,6 +4737,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4833,10 +4856,11 @@ } }, "node_modules/fetch-mock": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.0.0.tgz", - "integrity": "sha512-JSsjzoRN4rYqHa2/+8ushJGDsK9HGNTdBZo6Hrpu3KFN7Y03nRCt2VJ2WG4OUvyTUukOQ4TQIfjcFcEkMPGZ0Q==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.1.0.tgz", + "integrity": "sha512-xKcKpS9JIfA/vtYRGZtNXIYVHM7Ta/06e6tClSrJoOlU/46pribn1wmW2lDZkqY6MtloUSpeRzy+TbwSKxcvmQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/glob-to-regexp": "^0.4.4", "dequal": "^2.0.3", @@ -5123,16 +5147,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-stream": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", @@ -6591,14 +6605,11 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", @@ -6608,9 +6619,9 @@ "license": "ISC" }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", "dev": true, "license": "MIT", "dependencies": { @@ -10603,9 +10614,9 @@ } }, "node_modules/semantic-release": { - "version": "24.1.3", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.1.3.tgz", - "integrity": "sha512-Cb0Pm3Ye15u8k/B+7EnusMUSIIucAIEBD3QDRmmclv53KVyqmg1Lb3XPx0AMNxfJZEI+ZT+M+IXDyTrudK6Rew==", + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.0.tgz", + "integrity": "sha512-fQfn6e/aYToRtVJYKqneFM1Rg3KP2gh3wSWtpYsLlz6uaPKlISrTzvYAFn+mYWo07F0X1Cz5ucU89AVE8X1mbg==", "dev": true, "license": "MIT", "dependencies": { @@ -10943,9 +10954,9 @@ } }, "node_modules/semantic-release/node_modules/execa": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.1.tgz", - "integrity": "sha512-5eo/BRqZm3GYce+1jqX/tJ7duA2AnE39i88fuedNFUV8XxGxUpF3aWkBRfbUcjV49gCkvS/pzc0YrCPhaIewdg==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.1.tgz", + "integrity": "sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==", "dev": true, "license": "MIT", "dependencies": { @@ -11422,9 +11433,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", "dev": true, "license": "MIT" }, @@ -11866,9 +11877,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true, "license": "MIT" }, @@ -12235,14 +12246,15 @@ } }, "node_modules/vite-node": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", - "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", + "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -12257,30 +12269,31 @@ } }, "node_modules/vitest": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", - "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "2.1.3", - "@vitest/mocker": "2.1.3", - "@vitest/pretty-format": "^2.1.3", - "@vitest/runner": "2.1.3", - "@vitest/snapshot": "2.1.3", - "@vitest/spy": "2.1.3", - "@vitest/utils": "2.1.3", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", + "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.5", + "@vitest/mocker": "2.1.5", + "@vitest/pretty-format": "^2.1.5", + "@vitest/runner": "2.1.5", + "@vitest/snapshot": "2.1.5", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", + "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.3", + "vite-node": "2.1.5", "why-is-node-running": "^2.3.0" }, "bin": { @@ -12295,8 +12308,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.3", - "@vitest/ui": "2.1.3", + "@vitest/browser": "2.1.5", + "@vitest/ui": "2.1.5", "happy-dom": "*", "jsdom": "*" }, @@ -12755,7 +12768,7 @@ }, "packages/observables": { "name": "@rxjs-collection/observables", - "version": "1.0.4-beta.1", + "version": "1.0.4", "license": "MIT", "dependencies": { "@rxjs-collection/operators": "*", @@ -12764,7 +12777,7 @@ }, "packages/operators": { "name": "@rxjs-collection/operators", - "version": "1.0.3-beta.1", + "version": "1.0.5-beta.1", "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*", diff --git a/package.json b/package.json index fc33d9e..3752571 100644 --- a/package.json +++ b/package.json @@ -32,25 +32,25 @@ "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", - "@semantic-release/github": "11.0.0", + "@semantic-release/github": "11.0.1", "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.1", - "@vitest/coverage-v8": "2.1.3", + "@vitest/coverage-v8": "2.1.5", "commitlint": "19.5.0", - "eslint": "9.13.0", + "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-no-secrets": "1.0.2", + "eslint-plugin-no-secrets": "1.1.2", "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", - "fetch-mock": "12.0.0", + "fetch-mock": "12.1.0", "happy-dom": "15.7.4", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", - "semantic-release": "24.1.3", + "semantic-release": "24.2.0", "semantic-release-monorepo": "8.0.2", - "vitest": "2.1.3" + "vitest": "2.1.5" } } From 84b26147bae6a8ee2a6010a0f52884900d63761e Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Thu, 14 Nov 2024 09:48:13 +0100 Subject: [PATCH 03/17] fix(operators): added fetch tests --- .../operators/src/request/autoPagination.js | 2 +- .../src/request/autoPagination.test.js | 95 ++++++++++++++-- packages/operators/src/request/cache.js | 6 +- packages/operators/src/request/cache.test.js | 49 +++++++- .../src/request/concurrentRequest.js | 2 +- .../src/request/concurrentRequest.sample.js | 31 ----- .../src/request/concurrentRequest.test.js | 106 ++++++++++++------ .../operators/src/request/lazyPagination.js | 6 +- .../src/request/lazyPagination.test.js | 76 +++++++++++-- packages/operators/src/request/request.js | 11 +- packages/operators/src/request/response.js | 4 +- .../operators/src/request/response.test.js | 57 +++++++++- packages/operators/src/request/retry.js | 5 +- packages/operators/src/request/retry.test.js | 44 +++++--- 14 files changed, 376 insertions(+), 118 deletions(-) delete mode 100644 packages/operators/src/request/concurrentRequest.sample.js diff --git a/packages/operators/src/request/autoPagination.js b/packages/operators/src/request/autoPagination.js index 94b2512..345ac5a 100644 --- a/packages/operators/src/request/autoPagination.js +++ b/packages/operators/src/request/autoPagination.js @@ -5,7 +5,7 @@ import { request } from './request'; export const autoPagination = ({ resolveRoute }) => { return source => source.pipe( - concatMap(({ url }) => from(resolveRoute(url)).pipe(request(), getNext(resolveRoute, url))), + concatMap(url => from(resolveRoute(url)).pipe(request(), getNext(resolveRoute, url))), map(resp => resp.clone()) ); }; diff --git a/packages/operators/src/request/autoPagination.test.js b/packages/operators/src/request/autoPagination.test.js index c14e81a..25c78f9 100644 --- a/packages/operators/src/request/autoPagination.test.js +++ b/packages/operators/src/request/autoPagination.test.js @@ -1,18 +1,99 @@ -import { concatAll, map, of } from 'rxjs'; -import { beforeEach, describe, expect, test } from 'vitest'; +import { concatAll, concatMap, delay, from, map, of, toArray } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { log } from '../log'; -import { autoPagination } from './autoPagination'; import { resolveJSON } from './response'; -describe('auto pagination', function () { +describe('auto pagination - mocked', function () { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).to.eql(expected); + }); + beforeEach(function () { - // + vi.doMock('./request', importOriginal => ({ + request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) + })); + + Object.prototype.clone = vi.fn(); + vi.spyOn(Object.prototype, 'clone').mockImplementation(function (e) { + return { ...JSON.parse(JSON.stringify(this)) }; + }); }); - test('auto pagination', async function () { + afterEach(() => { + vi.doUnmock('./request'); + }); + + test('classic testing', async () => { + const { autoPagination } = await import('./autoPagination'); + + const triggerValues = { + a: { t: 2, v: { value: 'a', next: 1 } }, + b: { t: 5, v: { value: 'b', next: 2 } }, + c: { t: 3, v: { value: 'c', next: 3 } }, + d: { t: 1, v: { value: 'd', next: 4 } }, + e: { t: 4, v: { value: 'e', next: null } } + }; + + const expectedVal = Array.from(Object.entries(triggerValues)).map(([k, { v }]) => v); + + const triggerVal = Object.values(triggerValues); + await new Promise((done, error) => { + of(triggerVal[0]) + .pipe( + autoPagination({ + resolveRoute: (conf, resp) => + ((!resp || resp.next) && [triggerVal[resp?.next || 0]]) || [] + }), + toArray() + ) + .subscribe({ + next: e => expect(e).toStrictEqual(expectedVal), + complete: () => done(), + error: () => error() + }); + }); + }); + + test.skip('marble testing', async () => { + const { autoPagination } = await import('./autoPagination'); + + const triggerVal = { + a: { t: 2, v: { value: 'a', next: 'b' } }, + b: { t: 5, v: { value: 'b', next: 'c' } }, + c: { t: 3, v: { value: 'c', next: 'd' } }, + d: { t: 1, v: { value: 'd', next: 'e' } }, + e: { t: 4, v: { value: 'e', next: null } } + }; + + const expectedVal = Object.fromEntries( + Array.from(Object.entries(triggerVal)).map(([k, { v }]) => [k, v]) + ); + + testScheduler.run(({ cold, expectObservable }) => { + expectObservable( + cold('-a-------------------', triggerVal).pipe( + autoPagination({ + resolveRoute: (conf, resp) => + ((!resp || resp.next) && [triggerVal[resp?.next || 'a']]) || [] + }) + ) + ).toBe('---a----b--cd---e----', expectedVal); + }); + }); +}); + +describe.skip('auto pagination - demo', function () { + beforeEach(function () { + vi.resetModules(); + }); + + test('sample testing', async function () { + const { autoPagination } = await import('./autoPagination'); + return new Promise(done => { - return of({ url: new URL('https://dummyjson.com/products') }) + return of(new URL('https://dummyjson.com/products')) .pipe( autoPagination({ resolveRoute: async (url, resp) => { diff --git a/packages/operators/src/request/cache.js b/packages/operators/src/request/cache.js index a0f6e01..d5300db 100644 --- a/packages/operators/src/request/cache.js +++ b/packages/operators/src/request/cache.js @@ -1,4 +1,4 @@ -import { ReplaySubject, share, timer } from 'rxjs'; +import { ReplaySubject, share, tap, timer } from 'rxjs'; export const cache = ttl => { return source => @@ -6,7 +6,9 @@ export const cache = ttl => { share({ // TODO: check if a buffer size is neccessary connector: () => new ReplaySubject(), - resetOnComplete: () => timer(ttl) + // resetOnError: false, + resetOnComplete: () => timer(ttl), + resetOnRefCountZero: false }) ); }; diff --git a/packages/operators/src/request/cache.test.js b/packages/operators/src/request/cache.test.js index 8d7a436..f2835ea 100644 --- a/packages/operators/src/request/cache.test.js +++ b/packages/operators/src/request/cache.test.js @@ -1,21 +1,54 @@ import fetchMock from 'fetch-mock'; -import { defer, of, tap } from 'rxjs'; +import { defer, delay, from, interval, map, mapTo, of, tap, throttleTime } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { cache } from './cache'; import { requestText } from './request'; +describe('cache - mocked', function () { + beforeEach(function () { + // + }); + + afterEach(function () { + // + }); + + test.skip('cache resetted after 100ms', async function () { + let counter = 0; + const a = of(counter).pipe( + tap(e => console.log('U', e)), + cache(5) + ); + + defer(() => a) + .pipe(delay(2)) + .subscribe(e => console.log(e)); + defer(() => a) + .pipe(delay(2)) + .subscribe(e => console.log(e)); + + await new Promise(done => setTimeout(done), 500); + + defer(() => a) + .pipe(delay(100)) + .subscribe(e => console.log(e)); + + await new Promise(done => setTimeout(done), 1000); + }); +}); + describe('cache', function () { beforeEach(function () { let counter = 0; fetchMock.mockGlobal().get( 'https://httpbin.org/my-url-fast', - () => { - return new Response(++counter, { + () => + new Response(++counter, { status: 200, headers: { 'Content-type': 'plain/text' } - }); - }, + }), { delay: 0, repeat: 2 } ); }); @@ -25,7 +58,11 @@ describe('cache', function () { }); test('cache resetted after 100ms', async function () { - const a = of('https://httpbin.org/my-url-fast').pipe(requestText(), cache(1000)); + const a = of('https://httpbin.org/my-url-fast').pipe( + requestText(), + tap(() => console.log('CHECK')), + cache(1000) + ); await new Promise(done => { a.subscribe({ next: e => expect(e).toBe('1'), diff --git a/packages/operators/src/request/concurrentRequest.js b/packages/operators/src/request/concurrentRequest.js index 3dc770d..7fe7d60 100644 --- a/packages/operators/src/request/concurrentRequest.js +++ b/packages/operators/src/request/concurrentRequest.js @@ -1,4 +1,4 @@ -import { mergeMap, of, tap } from 'rxjs'; +import { mergeMap, of } from 'rxjs'; import { request } from './request'; diff --git a/packages/operators/src/request/concurrentRequest.sample.js b/packages/operators/src/request/concurrentRequest.sample.js deleted file mode 100644 index 696d315..0000000 --- a/packages/operators/src/request/concurrentRequest.sample.js +++ /dev/null @@ -1,31 +0,0 @@ -import { concatAll, map, of } from 'rxjs'; - -import { log } from '../log'; -import { concurrentRequest } from './concurrentRequest'; -import { resolveJSON } from './response'; - -await new Promise(done => { - of( - new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=20&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=30&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=40&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=50&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), - new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') - ) - .pipe( - concurrentRequest(4), - log(false), - resolveJSON(), - log(false), - map(({ products }) => products), - concatAll() - ) - .subscribe({ - next: e => console.log(e), - complete: () => done() - }); -}); diff --git a/packages/operators/src/request/concurrentRequest.test.js b/packages/operators/src/request/concurrentRequest.test.js index 208381a..803ed63 100644 --- a/packages/operators/src/request/concurrentRequest.test.js +++ b/packages/operators/src/request/concurrentRequest.test.js @@ -1,54 +1,96 @@ -import { concatMap, delay, of, tap, toArray } from 'rxjs'; +import { concatAll, concatMap, delay, from, map, of, tap, toArray } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { concurrentRequest } from './concurrentRequest'; +import { log } from '../log'; +import { resolveJSON } from './response'; -describe('multi fetch33', function () { +describe('concurrent request - mocked', function () { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).to.eql(expected); }); + const getTriggerValues = () => ({ + a: { t: 2, v: 'a' }, + b: { t: 5, v: 'b' }, + c: { t: 1, v: 'c' }, + d: { t: 3, v: 'd' }, + e: { t: 4, v: 'e' } + }); + beforeEach(function () { - vi.mock('./request.js', async importOriginal => { - return { - request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) - }; - }); + vi.doMock('./request', importOriginal => ({ + request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) + })); }); afterEach(() => { - vi.resetAllMocks(); + vi.doUnmock('./request'); }); - test('test', async () => { - const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(v => ({ v, t: Math.random() * 1000 })); - const sortedResult = [...values].sort((a, b) => a.t - b.t).map(({ v }) => v); + test('classic testing', async () => { + const { concurrentRequest } = await import('./concurrentRequest'); - await new Promise(done => { - of(...values) - .pipe(concurrentRequest(values.length), toArray()) - .subscribe({ next: e => expect(e).to.eql(sortedResult), complete: () => done() }); + const triggerVal = Object.values(getTriggerValues()); + const sortedVal = [...triggerVal].sort((a, b) => a.t - b.t).map(({ v }) => v); + + await new Promise((done, error) => { + from(triggerVal) + .pipe(concurrentRequest(triggerVal.length), toArray()) + .subscribe({ + next: e => expect(e).toStrictEqual(sortedVal), + complete: () => done(), + error: e => error(e) + }); }); }); - test('test2', async () => { - const triggerValues = { - a: { t: 2, v: 'a' }, - b: { t: 5, v: 'b' }, - c: { t: 0, v: 'c' }, - d: { t: 1, v: 'd' }, - e: { t: 1, v: 'd' } - }; - const expectedValues = Object.fromEntries( - Array.from(Object.entries(triggerValues)).map(([k, { v }]) => [k, v]) - ); + test('marble testing', async () => { + const { concurrentRequest } = await import('./concurrentRequest'); + + const triggerVal = getTriggerValues(); + const expectedVal = Object.fromEntries(Object.entries(triggerVal).map(([k, { v }]) => [k, v])); testScheduler.run(({ cold, expectObservable }) => { - expectObservable(cold('-a-b-c-(de)', triggerValues).pipe(concurrentRequest(3), tap())).toBe( - '---a-c--(bde)', - expectedValues - ); + expectObservable( + cold('-a-b-(cd)-e----', triggerVal).pipe(concurrentRequest(Object.keys(triggerVal).length)) + ).toBe('---a--c-(bd)--e', expectedVal); + }); + }); +}); + +describe.skip('concurrent request - demo', function () { + beforeAll(function () { + vi.resetModules(); + }); + + test('sample testing', async () => { + const { concurrentRequest } = await import('./concurrentRequest'); + + await new Promise(done => { + of( + new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=20&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=30&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=40&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=50&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), + new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') + ) + .pipe( + concurrentRequest(4), + log(false), + resolveJSON(), + log(false), + map(({ products }) => products), + concatAll() + ) + .subscribe({ + next: e => console.log(e), + complete: () => done() + }); }); }); }); diff --git a/packages/operators/src/request/lazyPagination.js b/packages/operators/src/request/lazyPagination.js index 3987ebc..cc21be2 100644 --- a/packages/operators/src/request/lazyPagination.js +++ b/packages/operators/src/request/lazyPagination.js @@ -1,11 +1,11 @@ -import { concatMap, map } from 'rxjs'; +import { concatMap, map, tap } from 'rxjs'; import { concurrentRequest } from './concurrentRequest'; -export const lazyPagination = ({ resolveRoute }) => { +export const lazyPagination = ({ pager, concurrent, resolveRoute }) => { return source => source.pipe( - concatMap(({ url, pager, concurrent }) => { + concatMap(({ url }) => { return pager.pipe( map(options => resolveRoute(url, options)), concurrentRequest(concurrent) diff --git a/packages/operators/src/request/lazyPagination.test.js b/packages/operators/src/request/lazyPagination.test.js index 0132230..0980813 100644 --- a/packages/operators/src/request/lazyPagination.test.js +++ b/packages/operators/src/request/lazyPagination.test.js @@ -1,22 +1,84 @@ -import { concatAll, map, of, Subject } from 'rxjs'; -import { beforeEach, describe, expect, test } from 'vitest'; +import { concatAll, concatMap, delay, map, of, Subject, tap } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; import { log } from '../log'; -import { lazyPagination } from './lazyPagination'; -import { resolveJSON } from './response'; -describe('lazy pagination operator', function () { +describe('lazy pagination - mocked', function () { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).to.eql(expected); + }); + beforeEach(function () { + vi.doMock('./request', importOriginal => ({ + request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) + })); + }); + + afterEach(() => { + vi.doUnmock('./request'); + }); + + test('classic testing', () => { // }); - test('successfull lazy pagination', async function () { + test('marble testing', async () => { + const { lazyPagination } = await import('./lazyPagination'); + + const pager = new Subject(); + + const triggerValues = { + a: () => pager.next({ value: 'a' }), + b: () => pager.next({ value: 'b' }), + c: () => pager.next({ value: 'c' }), + d: () => pager.next({ value: 'd' }), + e: () => pager.next({ value: 'e' }) + }; + + const responseValues = { + a: { t: 2, v: { value: 'a' } }, + b: { t: 5, v: { value: 'b' } }, + c: { t: 3, v: { value: 'c' } }, + d: { t: 1, v: { value: 'd' } }, + e: { t: 4, v: { value: 'e' } } + }; + + const expectedValues = Object.fromEntries( + Object.entries(responseValues).map(([key, v]) => [key, v.v]) + ); + + testScheduler.run(({ cold, expectObservable }) => { + expectObservable( + of({ url: 'https://example.com' }).pipe( + lazyPagination({ + pager, + concurrent: 5, + resolveRoute: (url, { value }) => responseValues[String(value)] + }) + ) + ).toBe('--daceb--------', expectedValues); + expectObservable(cold('-(abcde)--------', triggerValues).pipe(tap(fn => fn()))); + }); + }); +}); + +describe.skip('lazy pagination - demo', function () { + beforeAll(function () { + vi.resetModules(); + }); + + test('sample testing', async function () { + const { lazyPagination } = await import('./lazyPagination'); + const { resolveJSON } = await import('./response'); const pager = new Subject(); return new Promise(done => { - of({ url: new URL('https://dummyjson.com/products'), pager, concurrent: 4 }) + of({ url: new URL('https://dummyjson.com/products') }) .pipe( lazyPagination({ + pager, + concurrent: 4, resolveRoute: (url, { value, limit = 10 }) => { const newUrl = new URL(`${url}`); newUrl.searchParams.set('skip', value * limit); diff --git a/packages/operators/src/request/request.js b/packages/operators/src/request/request.js index ef7357f..123430b 100644 --- a/packages/operators/src/request/request.js +++ b/packages/operators/src/request/request.js @@ -1,13 +1,18 @@ -import { concatMap } from 'rxjs'; +import { concatMap, throwError } from 'rxjs'; -import { cache } from './cache'; import { resolveBlob, resolveJSON, resolveText } from './response'; import { networkRetry } from './retry'; export const request = () => { return source => source.pipe( - concatMap(req => fetch(req)), + concatMap(req => { + try { + return fetch(req); + } catch { + return throwError(() => new Error('Failed to fetch: resource not valid')); + } + }), networkRetry() ); }; diff --git a/packages/operators/src/request/response.js b/packages/operators/src/request/response.js index fac72f0..eef7390 100644 --- a/packages/operators/src/request/response.js +++ b/packages/operators/src/request/response.js @@ -1,5 +1,5 @@ import { shallowEqual } from 'fast-equals'; -import { concatMap, distinctUntilChanged, map } from 'rxjs'; +import { combineLatest, concatMap, distinctUntilChanged, from, map, of } from 'rxjs'; export const resolve = (type = 'json') => { return source => source.pipe(concatMap(e => e[String(type)]())); @@ -20,7 +20,7 @@ export const resolveBlob = () => { export const distinctUntilResponseChanged = () => { return source => source.pipe( - concatMap(async resp => [resp, await resp.clone().arrayBuffer()]), + concatMap(resp => combineLatest([of(resp), from(resp.clone().arrayBuffer())])), distinctUntilChanged(([, a], [, b]) => shallowEqual(new Uint8Array(a), new Uint8Array(b))), map(([resp]) => resp.clone()) ); diff --git a/packages/operators/src/request/response.test.js b/packages/operators/src/request/response.test.js index c4c2b66..54e1176 100644 --- a/packages/operators/src/request/response.test.js +++ b/packages/operators/src/request/response.test.js @@ -1,16 +1,21 @@ -import { of } from 'rxjs'; -import { afterEach, test, describe, beforeEach, expect } from 'vitest'; +import { map, of, tap } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { afterEach, test, describe, beforeEach, expect, vi } from 'vitest'; import { log } from '../log'; import { distinctUntilResponseChanged, resolveJSON, resolveText } from './response'; describe('response', function () { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).to.eql(expected); + }); + beforeEach(function () { // }); afterEach(function () { - // + vi.restoreAllMocks(); }); test('resolve json', () => { @@ -57,4 +62,50 @@ describe('response', function () { }); }); }); + + test('marble testing', async () => { + const triggerValues = { + a: createResponse('a', 'a'), + b: createResponse('b', 'a'), + c: createResponse('c', 'b'), + d: createResponse('d', 'b'), + e: createResponse('e', 'c'), + f: createResponse('f', 'a'), + g: createResponse('g', 'a'), + h: createResponse('h', 'b') + }; + + const expectedValues = Object.fromEntries( + await Promise.all( + Object.entries(triggerValues).map(async ([key, resp]) => { + return [`/${key}`, await resp.clone().arrayBuffer()]; + }) + ) + ); + + vi.spyOn(Response.prototype, 'arrayBuffer').mockImplementation(function (e) { + return [expectedValues[this.url]]; + }); + + testScheduler.run(({ cold, expectObservable }) => { + expectObservable( + cold('-a-b-c-d-e-f-g-h-', triggerValues).pipe( + distinctUntilResponseChanged(), + map(resp => resp.arrayBuffer()) + ) + ).toBe('-a---c---e-f---h-', { + a: triggerValues.a.arrayBuffer(), + c: triggerValues.c.arrayBuffer(), + e: triggerValues.e.arrayBuffer(), + f: triggerValues.f.arrayBuffer(), + h: triggerValues.h.arrayBuffer() + }); + }); + }); }); + +const createResponse = (key, value) => { + const resp = new Response(value); + Object.defineProperty(resp, 'url', { value: `/${key}` }); + return resp; +}; diff --git a/packages/operators/src/request/retry.js b/packages/operators/src/request/retry.js index 18d551a..e176618 100644 --- a/packages/operators/src/request/retry.js +++ b/packages/operators/src/request/retry.js @@ -24,8 +24,7 @@ export const networkRetry = ({ timeout = defaultTimeout, count } = {}) => { retry({ count, delay: () => determineDelayWhenOnline(timeout, ++counter) - }), - catchError(e => console.error(e)) + }) ); }; }; @@ -38,7 +37,7 @@ const determineDelayWhenOnline = (timeout, counter) => { tap(valid => (counter = counter * valid)), // continue only if all observables are valid filter(valid => valid), - tap(() => console.log(`retry: request - next: ${counter} in ${timeout(counter)}s`)), + tap(() => console.log(`retry: request - next: ${counter} in ${timeout(counter)}ms`)), delay(timeout(counter) || timeout) ); }; diff --git a/packages/operators/src/request/retry.test.js b/packages/operators/src/request/retry.test.js index fee16e3..b5551e1 100644 --- a/packages/operators/src/request/retry.test.js +++ b/packages/operators/src/request/retry.test.js @@ -1,30 +1,40 @@ -import { map, of } from 'rxjs'; +import { map } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { beforeEach, describe, expect, test } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { networkRetry } from './retry'; describe('request retry', function () { - let testScheduler; + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).deep.equal(expected); + }); - beforeEach(function () { - testScheduler = new TestScheduler((actual, expected) => { - expect(actual).deep.equal(expected); - }); + beforeEach(() => { + // }); - test('network retry', async function () { - let counter = 0; + afterEach(() => { + // + }); + + test('classic testing', () => { + // + }); - const mockObservable = of(null).pipe( - map(() => ({ ok: ++counter >= 3 })), - networkRetry({ timeout: () => 1000 }) - ); + test('marble testing', () => { + const error = new Response('', { status: 500 }); + const success = new Response('a', { status: 200 }); + const orderedResponses = [error, error, success]; - testScheduler.run(({ expectObservable }) => { - expectObservable(mockObservable).toBe('2000ms (a|)', { - a: { ok: true } - }); + testScheduler.run(({ cold, expectObservable }) => { + expectObservable( + cold('-a------', { + a: () => orderedResponses.shift() + }).pipe( + map(fn => fn()), + networkRetry({ timeout: () => 5 }) + ) + ).toBe('-------------a', { a: success }, new Error()); }); }); }); From ebaa6fcac87fbb58910a7d0a1818f8ec2fc57841 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:50:26 +0000 Subject: [PATCH 04/17] chore(deps): update dependency happy-dom to v15.10.2 [security] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index aebbdb2..8a2560e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", "fetch-mock": "12.1.0", - "happy-dom": "15.7.4", + "happy-dom": "15.10.2", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", @@ -5376,9 +5376,9 @@ } }, "node_modules/happy-dom": { - "version": "15.7.4", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.7.4.tgz", - "integrity": "sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==", + "version": "15.10.2", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.10.2.tgz", + "integrity": "sha512-NbA5XrSovenJIIcfixCREX3ZnV7yHP4phhbfuxxf4CPn+LZpz/jIM9EqJ2DrPwgVDSMoAKH3pZwQvkbsSiCrUw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3752571..4807687 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "eslint-plugin-security": "3.0.1", "eslint-plugin-vitest": "0.5.4", "fetch-mock": "12.1.0", - "happy-dom": "15.7.4", + "happy-dom": "15.10.2", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", From b73adfa3fdeba865f7de3423db99f52a046f0528 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:50:46 +0000 Subject: [PATCH 05/17] chore(deps): replace dependency eslint-plugin-vitest with @vitest/eslint-plugin 1.0.1 --- package-lock.json | 223 ++++++---------------------------------------- package.json | 2 +- 2 files changed, 26 insertions(+), 199 deletions(-) diff --git a/package-lock.json b/package-lock.json index aebbdb2..619516a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.1", "@vitest/coverage-v8": "2.1.5", + "@vitest/eslint-plugin": "1.0.1", "commitlint": "19.5.0", "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", @@ -28,7 +29,6 @@ "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", - "eslint-plugin-vitest": "0.5.4", "fetch-mock": "12.1.0", "happy-dom": "15.7.4", "husky": "9.1.6", @@ -2253,124 +2253,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", @@ -2512,37 +2394,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vitest/coverage-v8": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", @@ -2576,6 +2427,30 @@ } } }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.0.1.tgz", + "integrity": "sha512-albpL56cL9XMwHJWCWZqjDxkuDkBXBF3WpPGOv6q2WA3cipCP41cKEwfSGktoRNGmPN77wuX452O8pM+z+ApNw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", @@ -4362,54 +4237,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-plugin-vitest": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz", - "integrity": "sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^7.7.1" - }, - "engines": { - "node": "^18.0.0 || >= 20.0.0" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "vitest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-vitest/node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", diff --git a/package.json b/package.json index 3752571..d0e9843 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", - "eslint-plugin-vitest": "0.5.4", + "@vitest/eslint-plugin": "1.0.1", "fetch-mock": "12.1.0", "happy-dom": "15.7.4", "husky": "9.1.6", From ddba84e1d8f212136980a0b79494c7455f62b295 Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Thu, 14 Nov 2024 10:23:33 +0100 Subject: [PATCH 06/17] fix(eslint): updated config - renamed import --- eslint.config.js | 2 +- package-lock.json | 23 +++++++++-------------- package.json | 4 ++-- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 4bdceec..0e28f5e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,7 +8,7 @@ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' // https://github.com/eslint-community/eslint-plugin-security import eslintPluginSecurity from 'eslint-plugin-security'; // https://github.com/vitest-dev/eslint-plugin-vitest -import vitest from 'eslint-plugin-vitest'; +import vitest from '@vitest/eslint-plugin'; import globals from 'globals'; import eslintIgnores from './eslint.ignores.js'; diff --git a/package-lock.json b/package-lock.json index 6092670..b8b3565 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.1", "@vitest/coverage-v8": "2.1.5", - "@vitest/eslint-plugin": "1.0.1", + "@vitest/eslint-plugin": "1.1.10", "commitlint": "19.5.0", "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", @@ -30,7 +30,7 @@ "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "fetch-mock": "12.1.0", - "happy-dom": "15.10.2", + "happy-dom": "15.11.6", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", @@ -2428,11 +2428,10 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.0.1.tgz", - "integrity": "sha512-albpL56cL9XMwHJWCWZqjDxkuDkBXBF3WpPGOv6q2WA3cipCP41cKEwfSGktoRNGmPN77wuX452O8pM+z+ApNw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.10.tgz", + "integrity": "sha512-uScH5Kz5v32vvtQYB2iodpoPg2mGASK+VKpjlc2IUgE0+16uZKqVKi2vQxjxJ6sMCQLBs4xhBFZlmZBszsmfKQ==", "dev": true, - "license": "MIT", "peerDependencies": { "@typescript-eslint/utils": ">= 8.0", "eslint": ">= 8.57.0", @@ -2440,9 +2439,6 @@ "vitest": "*" }, "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, "typescript": { "optional": true }, @@ -5203,11 +5199,10 @@ } }, "node_modules/happy-dom": { - "version": "15.10.2", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.10.2.tgz", - "integrity": "sha512-NbA5XrSovenJIIcfixCREX3ZnV7yHP4phhbfuxxf4CPn+LZpz/jIM9EqJ2DrPwgVDSMoAKH3pZwQvkbsSiCrUw==", + "version": "15.11.6", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.11.6.tgz", + "integrity": "sha512-elX7iUTu+5+3b2+NGQc0L3eWyq9jKhuJJ4GpOMxxT/c2pg9O3L5H3ty2VECX0XXZgRmmRqXyOK8brA2hDI6LsQ==", "dev": true, - "license": "MIT", "dependencies": { "entities": "^4.5.0", "webidl-conversions": "^7.0.0", @@ -12604,7 +12599,7 @@ }, "packages/operators": { "name": "@rxjs-collection/operators", - "version": "1.0.5-beta.1", + "version": "1.0.5", "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*", diff --git a/package.json b/package.json index 120661f..c192ce1 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", - "@vitest/eslint-plugin": "1.0.1", + "@vitest/eslint-plugin": "1.1.10", "fetch-mock": "12.1.0", - "happy-dom": "15.10.2", + "happy-dom": "15.11.6", "husky": "9.1.6", "lint-staged": "15.2.10", "prettier": "3.3.3", From bd3c5ef521b87be36fde61e292f81e92297b16d6 Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Thu, 14 Nov 2024 10:35:24 +0100 Subject: [PATCH 07/17] fix(operators): optimized tests --- .../src/request/autoPagination.test.js | 19 +++++++-------- .../src/request/concurrentRequest.test.js | 24 +++++++++++-------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/operators/src/request/autoPagination.test.js b/packages/operators/src/request/autoPagination.test.js index 25c78f9..f9a8443 100644 --- a/packages/operators/src/request/autoPagination.test.js +++ b/packages/operators/src/request/autoPagination.test.js @@ -28,17 +28,16 @@ describe('auto pagination - mocked', function () { test('classic testing', async () => { const { autoPagination } = await import('./autoPagination'); - const triggerValues = { - a: { t: 2, v: { value: 'a', next: 1 } }, - b: { t: 5, v: { value: 'b', next: 2 } }, - c: { t: 3, v: { value: 'c', next: 3 } }, - d: { t: 1, v: { value: 'd', next: 4 } }, - e: { t: 4, v: { value: 'e', next: null } } - }; + const triggerVal = [ + { t: 2, v: { value: 'a', next: 1 } }, + { t: 5, v: { value: 'b', next: 2 } }, + { t: 3, v: { value: 'c', next: 3 } }, + { t: 1, v: { value: 'd', next: 4 } }, + { t: 4, v: { value: 'e', next: null } } + ]; - const expectedVal = Array.from(Object.entries(triggerValues)).map(([k, { v }]) => v); + const expectedVal = triggerVal.map(({ v }) => v); - const triggerVal = Object.values(triggerValues); await new Promise((done, error) => { of(triggerVal[0]) .pipe( @@ -56,7 +55,7 @@ describe('auto pagination - mocked', function () { }); }); - test.skip('marble testing', async () => { + test('marble testing', async () => { const { autoPagination } = await import('./autoPagination'); const triggerVal = { diff --git a/packages/operators/src/request/concurrentRequest.test.js b/packages/operators/src/request/concurrentRequest.test.js index 803ed63..8a3b7da 100644 --- a/packages/operators/src/request/concurrentRequest.test.js +++ b/packages/operators/src/request/concurrentRequest.test.js @@ -10,14 +10,6 @@ describe('concurrent request - mocked', function () { expect(actual).to.eql(expected); }); - const getTriggerValues = () => ({ - a: { t: 2, v: 'a' }, - b: { t: 5, v: 'b' }, - c: { t: 1, v: 'c' }, - d: { t: 3, v: 'd' }, - e: { t: 4, v: 'e' } - }); - beforeEach(function () { vi.doMock('./request', importOriginal => ({ request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) @@ -31,7 +23,13 @@ describe('concurrent request - mocked', function () { test('classic testing', async () => { const { concurrentRequest } = await import('./concurrentRequest'); - const triggerVal = Object.values(getTriggerValues()); + const triggerVal = [ + { t: 20, v: 'a' }, + { t: 50, v: 'b' }, + { t: 10, v: 'c' }, + { t: 30, v: 'd' }, + { t: 40, v: 'e' } + ]; const sortedVal = [...triggerVal].sort((a, b) => a.t - b.t).map(({ v }) => v); await new Promise((done, error) => { @@ -48,7 +46,13 @@ describe('concurrent request - mocked', function () { test('marble testing', async () => { const { concurrentRequest } = await import('./concurrentRequest'); - const triggerVal = getTriggerValues(); + const triggerVal = { + a: { t: 2, v: 'a' }, + b: { t: 5, v: 'b' }, + c: { t: 1, v: 'c' }, + d: { t: 3, v: 'd' }, + e: { t: 4, v: 'e' } + }; const expectedVal = Object.fromEntries(Object.entries(triggerVal).map(([k, { v }]) => [k, v])); testScheduler.run(({ cold, expectObservable }) => { From 3fb4fd0abef3aedc531b1399d33ec6cfa6a7a7ea Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 14 Nov 2024 09:39:39 +0000 Subject: [PATCH 08/17] chore(release): 1.0.6-beta.1 [skip ci] # [@rxjs-collection/operators-v1.0.6-beta.1](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.5...@rxjs-collection/operators-v1.0.6-beta.1) (2024-11-14) ### Bug Fixes * **fetch:** additional stuff ([4ab3e81](https://github.com/basics/rxjs-collection/commit/4ab3e8164d2fb347191b35e87c3ac33a2d1922f5)) * **operators:** added fetch tests ([84b2614](https://github.com/basics/rxjs-collection/commit/84b26147bae6a8ee2a6010a0f52884900d63761e)) * **operators:** optimized tests ([bd3c5ef](https://github.com/basics/rxjs-collection/commit/bd3c5ef521b87be36fde61e292f81e92297b16d6)) --- packages/operators/CHANGELOG.md | 9 +++++++++ packages/operators/package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/operators/CHANGELOG.md b/packages/operators/CHANGELOG.md index d5786e2..297e4e7 100644 --- a/packages/operators/CHANGELOG.md +++ b/packages/operators/CHANGELOG.md @@ -1,5 +1,14 @@ # Project Changelog +# [@rxjs-collection/operators-v1.0.6-beta.1](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.5...@rxjs-collection/operators-v1.0.6-beta.1) (2024-11-14) + + +### Bug Fixes + +* **fetch:** additional stuff ([4ab3e81](https://github.com/basics/rxjs-collection/commit/4ab3e8164d2fb347191b35e87c3ac33a2d1922f5)) +* **operators:** added fetch tests ([84b2614](https://github.com/basics/rxjs-collection/commit/84b26147bae6a8ee2a6010a0f52884900d63761e)) +* **operators:** optimized tests ([bd3c5ef](https://github.com/basics/rxjs-collection/commit/bd3c5ef521b87be36fde61e292f81e92297b16d6)) + # [@rxjs-collection/operators-v1.0.5](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.4...@rxjs-collection/operators-v1.0.5) (2024-10-25) diff --git a/packages/operators/package.json b/packages/operators/package.json index de47fe1..39dae6e 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -1,6 +1,6 @@ { "name": "@rxjs-collection/operators", - "version": "1.0.5", + "version": "1.0.6-beta.1", "description": "rxjs operators", "license": "MIT", "contributors": [ From 2b5f78bacbbae30ab02f62d1e603816cadba03ee Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Wed, 20 Nov 2024 14:34:54 +0100 Subject: [PATCH 09/17] fix(operators): marble testing --- package-lock.json | 21 ++++- packages/operators/package.json | 2 + packages/operators/src/log.js | 34 +++++-- .../src/request/autoPagination.test.js | 72 +++++++-------- packages/operators/src/request/cache.js | 4 +- packages/operators/src/request/cache.test.js | 46 ++++++++-- .../src/request/concurrentRequest.test.js | 43 ++++----- .../src/request/lazyPagination.test.js | 91 +++++++++---------- .../operators/src/request/polling.test.js | 73 +++++++++++++-- .../operators/src/request/request.test.js | 8 +- .../operators/src/request/response.test.js | 33 +++---- packages/operators/src/request/retry.test.js | 21 +++-- packages/test-utils/response.js | 5 + packages/test-utils/utils.js | 0 14 files changed, 292 insertions(+), 161 deletions(-) create mode 100644 packages/test-utils/response.js create mode 100644 packages/test-utils/utils.js diff --git a/package-lock.json b/package-lock.json index b8b3565..3067058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2627,6 +2627,14 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -3298,6 +3306,14 @@ "dev": true, "license": "ISC" }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/conventional-changelog-angular": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", @@ -11047,7 +11063,6 @@ "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", @@ -12599,10 +12614,12 @@ }, "packages/operators": { "name": "@rxjs-collection/operators", - "version": "1.0.5", + "version": "1.0.6-beta.1", "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*", + "ansi-colors": "^4.1.3", + "consola": "^3.2.3", "fast-equals": "5.0.1", "rxjs": "7.8.1" }, diff --git a/packages/operators/package.json b/packages/operators/package.json index 39dae6e..fcf81e5 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -19,6 +19,8 @@ }, "dependencies": { "@rxjs-collection/observables": "*", + "ansi-colors": "^4.1.3", + "consola": "^3.2.3", "fast-equals": "5.0.1", "rxjs": "7.8.1" }, diff --git a/packages/operators/src/log.js b/packages/operators/src/log.js index 553bf54..bbc5802 100644 --- a/packages/operators/src/log.js +++ b/packages/operators/src/log.js @@ -1,20 +1,30 @@ -import { Observable } from 'rxjs'; +import { bgGreen } from 'ansi-colors'; +import debug from 'debug'; +import { connectable, finalize, Observable, Subject } from 'rxjs'; -export const log = (active = true) => { - if (active) { +export const enableLog = tag => { + debug.enable(tag); +}; + +export const log = tag => { + var logger = debug(tag); + logger.log = console.log.bind(console); + var error = debug(`${tag}:error`); + + if (debug.enabled(tag)) { return source => { return new Observable(observer => { return source.subscribe( val => { - console.log(val); + logger(val); observer.next(val); }, err => { - console.error(err); + error(err); observer.error(err); }, () => { - console.log('%ccomplete', 'color: green'); + logger(bgGreen.bold('Complete!')); observer.complete(); } ); @@ -24,3 +34,15 @@ export const log = (active = true) => { return source => source; } }; + +export const logResult = (tag, observable) => { + return new Promise(done => { + connectable( + observable.pipe( + log(tag), + finalize(() => done()) + ), + { connector: () => new Subject() } + ).connect(); + }); +}; diff --git a/packages/operators/src/request/autoPagination.test.js b/packages/operators/src/request/autoPagination.test.js index f9a8443..4601294 100644 --- a/packages/operators/src/request/autoPagination.test.js +++ b/packages/operators/src/request/autoPagination.test.js @@ -1,16 +1,16 @@ import { concatAll, concatMap, delay, from, map, of, toArray } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { log } from '../log'; +import { log, logOutput, logResult } from '../log'; import { resolveJSON } from './response'; -describe('auto pagination - mocked', function () { +describe('auto pagination - mocked', () => { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).to.eql(expected); }); - beforeEach(function () { + beforeEach(() => { vi.doMock('./request', importOriginal => ({ request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) })); @@ -25,6 +25,10 @@ describe('auto pagination - mocked', function () { vi.doUnmock('./request'); }); + afterAll(() => { + vi.resetModules(); + }); + test('classic testing', async () => { const { autoPagination } = await import('./autoPagination'); @@ -76,47 +80,41 @@ describe('auto pagination - mocked', function () { autoPagination({ resolveRoute: (conf, resp) => ((!resp || resp.next) && [triggerVal[resp?.next || 'a']]) || [] - }) + }), + log('marble:result') ) ).toBe('---a----b--cd---e----', expectedVal); }); }); }); -describe.skip('auto pagination - demo', function () { - beforeEach(function () { - vi.resetModules(); - }); - - test('sample testing', async function () { +describe('auto pagination - demo', () => { + test('sample testing', async () => { const { autoPagination } = await import('./autoPagination'); - return new Promise(done => { - return of(new URL('https://dummyjson.com/products')) - .pipe( - autoPagination({ - resolveRoute: async (url, resp) => { - const data = (await resp?.json()) || { skip: -10, limit: 10 }; - - if (!data.total || data.total > data.skip + data.limit) { - const newUrl = new URL(`${url}`); - newUrl.searchParams.set('skip', data.skip + data.limit); - newUrl.searchParams.set('limit', data.limit); - newUrl.searchParams.set('select', 'title,price'); - return newUrl; - } + await logResult( + 'demo', + of(new URL('https://dummyjson.com/products')).pipe( + autoPagination({ + resolveRoute: async (url, resp) => { + const data = (await resp?.json()) || { skip: -10, limit: 10 }; + + if (!data.total || data.total > data.skip + data.limit) { + const newUrl = new URL(`${url}`); + newUrl.searchParams.set('skip', data.skip + data.limit); + newUrl.searchParams.set('limit', data.limit); + newUrl.searchParams.set('select', 'title,price'); + return newUrl; } - }), - log(false), - resolveJSON(), - log(false), - map(({ products }) => products), - concatAll() - ) - .subscribe({ - next: e => console.log(e), - complete: () => done() - }); - }); + } + }), + log('demo:response'), + resolveJSON(), + log('demo:response:json'), + map(({ products }) => products), + log('demo:response:result'), + concatAll() + ) + ); }); }); diff --git a/packages/operators/src/request/cache.js b/packages/operators/src/request/cache.js index d5300db..2f16c1b 100644 --- a/packages/operators/src/request/cache.js +++ b/packages/operators/src/request/cache.js @@ -4,11 +4,9 @@ export const cache = ttl => { return source => source.pipe( share({ - // TODO: check if a buffer size is neccessary connector: () => new ReplaySubject(), - // resetOnError: false, resetOnComplete: () => timer(ttl), - resetOnRefCountZero: false + resetOnRefCountZero: () => timer(ttl) }) ); }; diff --git a/packages/operators/src/request/cache.test.js b/packages/operators/src/request/cache.test.js index f2835ea..0969cb7 100644 --- a/packages/operators/src/request/cache.test.js +++ b/packages/operators/src/request/cache.test.js @@ -1,21 +1,49 @@ import fetchMock from 'fetch-mock'; -import { defer, delay, from, interval, map, mapTo, of, tap, throttleTime } from 'rxjs'; +import { defer, delay, map, of, tap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { cache } from './cache'; import { requestText } from './request'; -describe('cache - mocked', function () { - beforeEach(function () { +describe('cache - mocked', () => { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).deep.equal(expected); + }); + + beforeEach(() => { // }); - afterEach(function () { + afterEach(() => { // }); - test.skip('cache resetted after 100ms', async function () { + test('marble testing', () => { + const initial = new Response('initial', { status: 200 }); + const updated = new Response('updated', { status: 200 }); + const orderedResponses = [initial, updated]; + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a-----------', { + a: () => orderedResponses.shift() + }).pipe( + map(fn => fn()), + cache(2) + ); + + const unsubA = '-^!'; + expectObservable(stream, unsubA).toBe('-a', { a: initial }, new Error()); + + const unsubB = '----^!'; + expectObservable(stream, unsubB).toBe('----a', { a: initial }, new Error()); + + const unsubC = '---------^--!'; + expectObservable(stream, unsubC).toBe('---------a', { a: updated }, new Error()); + }); + }); + + test('cache resetted after 100ms', async () => { let counter = 0; const a = of(counter).pipe( tap(e => console.log('U', e)), @@ -39,8 +67,8 @@ describe('cache - mocked', function () { }); }); -describe('cache', function () { - beforeEach(function () { +describe('cache', () => { + beforeEach(() => { let counter = 0; fetchMock.mockGlobal().get( 'https://httpbin.org/my-url-fast', @@ -53,11 +81,11 @@ describe('cache', function () { ); }); - afterEach(function () { + afterEach(() => { fetchMock.unmockGlobal(); }); - test('cache resetted after 100ms', async function () { + test('cache resetted after 100ms', async () => { const a = of('https://httpbin.org/my-url-fast').pipe( requestText(), tap(() => console.log('CHECK')), diff --git a/packages/operators/src/request/concurrentRequest.test.js b/packages/operators/src/request/concurrentRequest.test.js index 8a3b7da..7aa6ac6 100644 --- a/packages/operators/src/request/concurrentRequest.test.js +++ b/packages/operators/src/request/concurrentRequest.test.js @@ -1,16 +1,16 @@ import { concatAll, concatMap, delay, from, map, of, tap, toArray } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { log } from '../log'; +import { log, logResult } from '../log'; import { resolveJSON } from './response'; -describe('concurrent request - mocked', function () { +describe('concurrent request - mocked', () => { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).to.eql(expected); }); - beforeEach(function () { + beforeEach(() => { vi.doMock('./request', importOriginal => ({ request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) })); @@ -20,6 +20,10 @@ describe('concurrent request - mocked', function () { vi.doUnmock('./request'); }); + afterAll(function () { + vi.resetModules(); + }); + test('classic testing', async () => { const { concurrentRequest } = await import('./concurrentRequest'); @@ -63,15 +67,12 @@ describe('concurrent request - mocked', function () { }); }); -describe.skip('concurrent request - demo', function () { - beforeAll(function () { - vi.resetModules(); - }); - +describe('concurrent request - demo', () => { test('sample testing', async () => { const { concurrentRequest } = await import('./concurrentRequest'); - await new Promise(done => { + await logResult( + 'demo', of( new URL('https://dummyjson.com/products?limit=10&skip=0&select=title,price'), new URL('https://dummyjson.com/products?limit=10&skip=10&select=title,price'), @@ -82,19 +83,15 @@ describe.skip('concurrent request - demo', function () { new URL('https://dummyjson.com/products?limit=10&skip=60&select=title,price'), new URL('https://dummyjson.com/products?limit=10&skip=70&select=title,price'), new URL('https://dummyjson.com/products?limit=10&skip=80&select=title,price') + ).pipe( + concurrentRequest(4), + log('demo:response'), + resolveJSON(), + log('demo:response:json'), + map(({ products }) => products), + log('demo:response:result'), + concatAll() ) - .pipe( - concurrentRequest(4), - log(false), - resolveJSON(), - log(false), - map(({ products }) => products), - concatAll() - ) - .subscribe({ - next: e => console.log(e), - complete: () => done() - }); - }); + ); }); }); diff --git a/packages/operators/src/request/lazyPagination.test.js b/packages/operators/src/request/lazyPagination.test.js index 0980813..5167ba5 100644 --- a/packages/operators/src/request/lazyPagination.test.js +++ b/packages/operators/src/request/lazyPagination.test.js @@ -1,15 +1,15 @@ import { concatAll, concatMap, delay, map, of, Subject, tap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { log } from '../log'; +import { log, logResult } from '../log'; -describe('lazy pagination - mocked', function () { +describe('lazy pagination - mocked', () => { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).to.eql(expected); }); - beforeEach(function () { + beforeEach(() => { vi.doMock('./request', importOriginal => ({ request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) })); @@ -19,6 +19,10 @@ describe('lazy pagination - mocked', function () { vi.doUnmock('./request'); }); + afterAll(() => { + vi.resetModules(); + }); + test('classic testing', () => { // }); @@ -63,51 +67,46 @@ describe('lazy pagination - mocked', function () { }); }); -describe.skip('lazy pagination - demo', function () { - beforeAll(function () { - vi.resetModules(); - }); - - test('sample testing', async function () { +describe('lazy pagination - demo', () => { + test('sample testing', async () => { const { lazyPagination } = await import('./lazyPagination'); const { resolveJSON } = await import('./response'); + const pager = new Subject(); - return new Promise(done => { - of({ url: new URL('https://dummyjson.com/products') }) - .pipe( - lazyPagination({ - pager, - concurrent: 4, - resolveRoute: (url, { value, limit = 10 }) => { - const newUrl = new URL(`${url}`); - newUrl.searchParams.set('skip', value * limit); - newUrl.searchParams.set('limit', limit); - newUrl.searchParams.set('select', 'title,price'); - return newUrl; - } - }), - log(false), - resolveJSON(), - log(false), - map(({ products }) => products), - concatAll(), - log(false) - ) - .subscribe({ - next: e => console.log(e), - complete: () => done() - }); - - pager.next({ value: 2 }); - pager.next({ value: 3 }); - pager.next({ value: 12 }); - pager.next({ value: 5 }); - pager.next({ value: 6 }); - pager.next({ value: 7 }); - pager.next({ value: 8 }); - pager.next({ value: 9 }); - pager.complete(); - }); + const result = logResult( + 'demo', + of({ url: new URL('https://dummyjson.com/products') }).pipe( + lazyPagination({ + pager, + concurrent: 4, + resolveRoute: (url, { value, limit = 10 }) => { + const newUrl = new URL(`${url}`); + newUrl.searchParams.set('skip', value * limit); + newUrl.searchParams.set('limit', limit); + newUrl.searchParams.set('select', 'title,price'); + return newUrl; + } + }), + log('demo:response'), + resolveJSON(), + log('demo:response:json'), + map(({ products }) => products), + log('demo:response:result'), + concatAll() + ) + ); + + pager.next({ value: 2 }); + pager.next({ value: 3 }); + pager.next({ value: 12 }); + pager.next({ value: 5 }); + pager.next({ value: 6 }); + pager.next({ value: 7 }); + pager.next({ value: 8 }); + pager.next({ value: 9 }); + pager.complete(); + + await result; }); }); diff --git a/packages/operators/src/request/polling.test.js b/packages/operators/src/request/polling.test.js index 8904ce7..825e7f3 100644 --- a/packages/operators/src/request/polling.test.js +++ b/packages/operators/src/request/polling.test.js @@ -1,12 +1,72 @@ import fetchMock from 'fetch-mock'; -import { of, take } from 'rxjs'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { concatMap, map, of, take, tap } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { createResponse } from '../../../test-utils/response'; import { log } from '../log'; -import { polling } from './polling'; import { resolveJSON } from './response'; -describe('polling', function () { +describe('polling - mocked', () => { + let triggerVal; + let expectedArrayBuffer; + + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).to.eql(expected); + }); + + beforeEach(async () => { + triggerVal = [ + createResponse('https://example.com/', 'a'), + createResponse('https://example.com/', 'a'), + createResponse('https://example.com/', 'a'), + createResponse('https://example.com/', 'b'), + createResponse('https://example.com/', 'b'), + createResponse('https://example.com/', 'c'), + createResponse('https://example.com/', 'c') + ]; + + expectedArrayBuffer = await Promise.all(triggerVal.map(e => e.clone().arrayBuffer())); + + let counter = 0; + vi.doMock('./request', importOriginal => ({ + request: () => source => source.pipe(map(() => triggerVal[counter++])) + })); + + vi.spyOn(Response.prototype, 'arrayBuffer').mockImplementation(() => { + return of(expectedArrayBuffer[counter - 1]); + }); + }); + + afterEach(() => { + vi.doUnmock('./request'); + }); + + afterAll(() => { + vi.restoreAllMocks(); + vi.resetModules(); + }); + + test('marble testing', async () => { + const { polling } = await import('./polling'); + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a------------', { a: 'https://example.com/' }).pipe( + polling(2), + concatMap(e => e.arrayBuffer()) + ); + + const unsubA = '^------------!'; + expectObservable(stream, unsubA).toBe('a-----b---c--', { + a: expectedArrayBuffer[0], + b: expectedArrayBuffer[3], + c: expectedArrayBuffer[5] + }); + }); + }); +}); + +describe('classic testing', () => { beforeEach(function () { let counter = 0; fetchMock.mockGlobal().get('https://httpbin.org/my-url-fast', () => { @@ -23,11 +83,12 @@ describe('polling', function () { }); }); - afterEach(function () { + afterEach(() => { fetchMock.unmockGlobal(); }); - test('auto polling', async function () { + test('auto polling', async () => { + const { polling } = await import('./polling'); const expected = [{ hello: 'fast world' }, { hello: 'faster world' }]; return new Promise(done => { diff --git a/packages/operators/src/request/request.test.js b/packages/operators/src/request/request.test.js index 5c7c88f..295d367 100644 --- a/packages/operators/src/request/request.test.js +++ b/packages/operators/src/request/request.test.js @@ -7,7 +7,7 @@ import { log } from '../log.js'; import { request, requestJSON } from './request.js'; import { resolveJSON } from './response.js'; -describe('request observable with default ', function () { +describe('request observable with default ', () => { test('successfull upload', async () => { const formData = new FormData(); formData.set( @@ -42,8 +42,8 @@ describe('request observable with default ', function () { }); }); -describe('request observable with default operators', function () { - beforeEach(function () { +describe('request observable with default operators', () => { + beforeEach(() => { let counter = 0; fetchMock.mockGlobal().get( 'https://httpbin.org/my-url-fast', @@ -68,7 +68,7 @@ describe('request observable with default operators', function () { ); }); - afterEach(function () { + afterEach(() => { fetchMock.unmockGlobal(); }); diff --git a/packages/operators/src/request/response.test.js b/packages/operators/src/request/response.test.js index 54e1176..54e9d26 100644 --- a/packages/operators/src/request/response.test.js +++ b/packages/operators/src/request/response.test.js @@ -2,19 +2,20 @@ import { map, of, tap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { afterEach, test, describe, beforeEach, expect, vi } from 'vitest'; +import { createResponse } from '../../../test-utils/response'; import { log } from '../log'; import { distinctUntilResponseChanged, resolveJSON, resolveText } from './response'; -describe('response', function () { +describe('response', () => { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).to.eql(expected); }); - beforeEach(function () { + beforeEach(() => { // }); - afterEach(function () { + afterEach(() => { vi.restoreAllMocks(); }); @@ -65,14 +66,14 @@ describe('response', function () { test('marble testing', async () => { const triggerValues = { - a: createResponse('a', 'a'), - b: createResponse('b', 'a'), - c: createResponse('c', 'b'), - d: createResponse('d', 'b'), - e: createResponse('e', 'c'), - f: createResponse('f', 'a'), - g: createResponse('g', 'a'), - h: createResponse('h', 'b') + a: createResponse('/a', 'a'), + b: createResponse('/b', 'a'), + c: createResponse('/c', 'b'), + d: createResponse('/d', 'b'), + e: createResponse('/e', 'c'), + f: createResponse('/f', 'a'), + g: createResponse('/g', 'a'), + h: createResponse('/h', 'b') }; const expectedValues = Object.fromEntries( @@ -104,8 +105,8 @@ describe('response', function () { }); }); -const createResponse = (key, value) => { - const resp = new Response(value); - Object.defineProperty(resp, 'url', { value: `/${key}` }); - return resp; -}; +const pick = (obj, arr) => + arr.reduce( + (acc, record) => (record in obj && (acc[String(record)] = obj[String(record)]), acc), + {} + ); diff --git a/packages/operators/src/request/retry.test.js b/packages/operators/src/request/retry.test.js index b5551e1..4b8dd0d 100644 --- a/packages/operators/src/request/retry.test.js +++ b/packages/operators/src/request/retry.test.js @@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, test } from 'vitest'; import { networkRetry } from './retry'; -describe('request retry', function () { +describe('request retry', () => { const testScheduler = new TestScheduler((actual, expected) => { expect(actual).deep.equal(expected); }); @@ -27,14 +27,17 @@ describe('request retry', function () { const orderedResponses = [error, error, success]; testScheduler.run(({ cold, expectObservable }) => { - expectObservable( - cold('-a------', { - a: () => orderedResponses.shift() - }).pipe( - map(fn => fn()), - networkRetry({ timeout: () => 5 }) - ) - ).toBe('-------------a', { a: success }, new Error()); + // retry is repeating the sequence + // if you define a delay, you have to add the delay to the subscribe multiple times (num retries) + const stream = cold('a----------', { + a: () => orderedResponses.shift() + }).pipe( + map(fn => fn()), + networkRetry({ timeout: () => 5 }) + ); + + const unsubA = '^----------!'; + expectObservable(stream, unsubA).toBe('----------a', { a: success }, new Error()); }); }); }); diff --git a/packages/test-utils/response.js b/packages/test-utils/response.js new file mode 100644 index 0000000..04d6388 --- /dev/null +++ b/packages/test-utils/response.js @@ -0,0 +1,5 @@ +export const createResponse = (url, content, options = { status: 200 }) => { + const resp = new Response(content, options); + Object.defineProperty(resp, 'url', { value: url }); + return resp; +}; diff --git a/packages/test-utils/utils.js b/packages/test-utils/utils.js new file mode 100644 index 0000000..e69de29 From aabbbd89bd76ca57cfd20b747890fcc74ee01227 Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Sun, 24 Nov 2024 21:03:13 +0100 Subject: [PATCH 10/17] fix(operators): cleanup and standardize tests --- .vscode/extensions.json | 3 +- packages/mock/async.js | 3 + packages/{test-utils => mock}/network.js | 0 packages/mock/response.js | 15 ++ packages/observables/src/dom/window.test.js | 2 +- packages/operators/src/log.js | 10 +- .../src/request/autoPagination.test.js | 118 ++++------ packages/operators/src/request/cache.js | 2 +- packages/operators/src/request/cache.test.js | 94 +------- .../src/request/concurrentRequest.test.js | 73 +++---- .../operators/src/request/lazyPagination.js | 4 +- .../src/request/lazyPagination.test.js | 73 ++++--- .../operators/src/request/polling.test.js | 115 +++------- packages/operators/src/request/request.js | 20 +- .../operators/src/request/request.test.js | 205 +++++++++++------- packages/operators/src/request/response.js | 2 +- .../operators/src/request/response.test.js | 131 +++++------ packages/operators/src/request/retry.js | 13 +- packages/operators/src/request/retry.test.js | 22 +- packages/test-utils/response.js | 5 - packages/test-utils/utils.js | 0 21 files changed, 390 insertions(+), 520 deletions(-) create mode 100644 packages/mock/async.js rename packages/{test-utils => mock}/network.js (100%) create mode 100644 packages/mock/response.js delete mode 100644 packages/test-utils/response.js delete mode 100644 packages/test-utils/utils.js diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0241e0f..1c5a9a0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -13,6 +13,7 @@ "formulahendry.auto-close-tag", "formulahendry.auto-rename-tag", "naumovs.color-highlight", - "humao.rest-client" + "humao.rest-client", + "techer.open-in-browser" ] } diff --git a/packages/mock/async.js b/packages/mock/async.js new file mode 100644 index 0000000..dc38747 --- /dev/null +++ b/packages/mock/async.js @@ -0,0 +1,3 @@ +import { of } from 'rxjs'; + +export const mockAsync = v => of(v); diff --git a/packages/test-utils/network.js b/packages/mock/network.js similarity index 100% rename from packages/test-utils/network.js rename to packages/mock/network.js diff --git a/packages/mock/response.js b/packages/mock/response.js new file mode 100644 index 0000000..5460723 --- /dev/null +++ b/packages/mock/response.js @@ -0,0 +1,15 @@ +import { vi } from 'vitest'; + +import { mockAsync } from './async'; + +export const mockResponse = () => { + return vi.fn((e, url) => ({ + url: url, + clone: () => new Response(e), + json: () => mockAsync(e), + text: () => mockAsync(e), + blob: () => mockAsync(e), + arrayBuffer: () => mockAsync(e), + ok: true + })); +}; diff --git a/packages/observables/src/dom/window.test.js b/packages/observables/src/dom/window.test.js index 2c7d871..407926d 100644 --- a/packages/observables/src/dom/window.test.js +++ b/packages/observables/src/dom/window.test.js @@ -2,7 +2,7 @@ import { tap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { beforeEach, test, expect, describe, afterEach } from 'vitest'; -import { mockOffline, mockOnline, mockReset } from '../../../test-utils/network.js'; +import { mockOffline, mockOnline, mockReset } from '../../../mock/network.js'; import { connectionObservable } from './window.js'; // HINT: https://betterprogramming.pub/rxjs-testing-write-unit-tests-for-observables-603af959e251 diff --git a/packages/operators/src/log.js b/packages/operators/src/log.js index bbc5802..e23b698 100644 --- a/packages/operators/src/log.js +++ b/packages/operators/src/log.js @@ -14,20 +14,20 @@ export const log = tag => { if (debug.enabled(tag)) { return source => { return new Observable(observer => { - return source.subscribe( - val => { + return source.subscribe({ + next: val => { logger(val); observer.next(val); }, - err => { + error: err => { error(err); observer.error(err); }, - () => { + complete: () => { logger(bgGreen.bold('Complete!')); observer.complete(); } - ); + }); }); }; } else { diff --git a/packages/operators/src/request/autoPagination.test.js b/packages/operators/src/request/autoPagination.test.js index 4601294..9f23dd4 100644 --- a/packages/operators/src/request/autoPagination.test.js +++ b/packages/operators/src/request/autoPagination.test.js @@ -1,86 +1,60 @@ -import { concatAll, concatMap, delay, from, map, of, toArray } from 'rxjs'; +import { concatAll, delay, from, map, of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterAll, afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { log, logOutput, logResult } from '../log'; +import { mockAsync } from '../../../mock/async'; +import { mockResponse } from '../../../mock/response'; +import { log, logResult } from '../log'; import { resolveJSON } from './response'; -describe('auto pagination - mocked', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).to.eql(expected); - }); +describe('auto pagination', () => { + let testScheduler; - beforeEach(() => { - vi.doMock('./request', importOriginal => ({ - request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) - })); + beforeAll(() => { + vi.spyOn(global, 'fetch').mockImplementation(({ v, t }) => mockAsync(v).pipe(delay(t))); - Object.prototype.clone = vi.fn(); - vi.spyOn(Object.prototype, 'clone').mockImplementation(function (e) { - return { ...JSON.parse(JSON.stringify(this)) }; - }); + global.Response = mockResponse(); }); - afterEach(() => { - vi.doUnmock('./request'); + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected)); }); afterAll(() => { - vi.resetModules(); + vi.restoreAllMocks(); }); - test('classic testing', async () => { + test('default', async () => { const { autoPagination } = await import('./autoPagination'); - const triggerVal = [ - { t: 2, v: { value: 'a', next: 1 } }, - { t: 5, v: { value: 'b', next: 2 } }, - { t: 3, v: { value: 'c', next: 3 } }, - { t: 1, v: { value: 'd', next: 4 } }, - { t: 4, v: { value: 'e', next: null } } - ]; - - const expectedVal = triggerVal.map(({ v }) => v); - - await new Promise((done, error) => { - of(triggerVal[0]) - .pipe( - autoPagination({ - resolveRoute: (conf, resp) => - ((!resp || resp.next) && [triggerVal[resp?.next || 0]]) || [] - }), - toArray() - ) - .subscribe({ - next: e => expect(e).toStrictEqual(expectedVal), - complete: () => done(), - error: () => error() - }); - }); - }); - - test('marble testing', async () => { - const { autoPagination } = await import('./autoPagination'); + const expectedVal = { + a: { value: 'a', next: 'b' }, + b: { value: 'b', next: 'c' }, + c: { value: 'c', next: 'd' }, + d: { value: 'd', next: 'e' }, + e: { value: 'e', next: null } + }; const triggerVal = { - a: { t: 2, v: { value: 'a', next: 'b' } }, - b: { t: 5, v: { value: 'b', next: 'c' } }, - c: { t: 3, v: { value: 'c', next: 'd' } }, - d: { t: 1, v: { value: 'd', next: 'e' } }, - e: { t: 4, v: { value: 'e', next: null } } + a: { t: 2, v: new Response(expectedVal.a) }, + b: { t: 5, v: new Response(expectedVal.b) }, + c: { t: 3, v: new Response(expectedVal.c) }, + d: { t: 1, v: new Response(expectedVal.d) }, + e: { t: 4, v: new Response(expectedVal.e) } }; - const expectedVal = Object.fromEntries( - Array.from(Object.entries(triggerVal)).map(([k, { v }]) => [k, v]) - ); - testScheduler.run(({ cold, expectObservable }) => { expectObservable( - cold('-a-------------------', triggerVal).pipe( + cold('-a-------------------', { a: 'a' }).pipe( autoPagination({ - resolveRoute: (conf, resp) => - ((!resp || resp.next) && [triggerVal[resp?.next || 'a']]) || [] + resolveRoute: (url, resp) => { + if (resp) { + return from(resp.json()).pipe(map(({ next }) => triggerVal[String(next)])); + } + return of(triggerVal[String(url)]); + } }), + resolveJSON(), log('marble:result') ) ).toBe('---a----b--cd---e----', expectedVal); @@ -89,23 +63,25 @@ describe('auto pagination - mocked', () => { }); describe('auto pagination - demo', () => { - test('sample testing', async () => { + test('sample', async () => { const { autoPagination } = await import('./autoPagination'); await logResult( 'demo', of(new URL('https://dummyjson.com/products')).pipe( autoPagination({ - resolveRoute: async (url, resp) => { - const data = (await resp?.json()) || { skip: -10, limit: 10 }; - - if (!data.total || data.total > data.skip + data.limit) { - const newUrl = new URL(`${url}`); - newUrl.searchParams.set('skip', data.skip + data.limit); - newUrl.searchParams.set('limit', data.limit); - newUrl.searchParams.set('select', 'title,price'); - return newUrl; - } + resolveRoute: (url, resp) => { + return from(resp?.json() || of({ skip: -10, limit: 10 })).pipe( + map(data => { + if (!data.total || data.total > data.skip + data.limit) { + const newUrl = new URL(`${url}`); + newUrl.searchParams.set('skip', data.skip + data.limit); + newUrl.searchParams.set('limit', data.limit); + newUrl.searchParams.set('select', 'title,price'); + return newUrl; + } + }) + ); } }), log('demo:response'), diff --git a/packages/operators/src/request/cache.js b/packages/operators/src/request/cache.js index 2f16c1b..0ab8e88 100644 --- a/packages/operators/src/request/cache.js +++ b/packages/operators/src/request/cache.js @@ -1,4 +1,4 @@ -import { ReplaySubject, share, tap, timer } from 'rxjs'; +import { ReplaySubject, share, timer } from 'rxjs'; export const cache = ttl => { return source => diff --git a/packages/operators/src/request/cache.test.js b/packages/operators/src/request/cache.test.js index 0969cb7..bdaa853 100644 --- a/packages/operators/src/request/cache.test.js +++ b/packages/operators/src/request/cache.test.js @@ -1,31 +1,23 @@ -import fetchMock from 'fetch-mock'; -import { defer, delay, map, of, tap } from 'rxjs'; +import { map } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { beforeEach, describe, expect, test } from 'vitest'; import { cache } from './cache'; -import { requestText } from './request'; -describe('cache - mocked', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).deep.equal(expected); - }); +describe('cache', () => { + let testScheduler; beforeEach(() => { - // - }); - - afterEach(() => { - // + testScheduler = new TestScheduler((actual, expected) => expect(actual).deep.equal(expected)); }); - test('marble testing', () => { + test('default', () => { const initial = new Response('initial', { status: 200 }); const updated = new Response('updated', { status: 200 }); const orderedResponses = [initial, updated]; testScheduler.run(({ cold, expectObservable }) => { - const stream = cold('a-----------', { + const stream = cold('a', { a: () => orderedResponses.shift() }).pipe( map(fn => fn()), @@ -42,76 +34,4 @@ describe('cache - mocked', () => { expectObservable(stream, unsubC).toBe('---------a', { a: updated }, new Error()); }); }); - - test('cache resetted after 100ms', async () => { - let counter = 0; - const a = of(counter).pipe( - tap(e => console.log('U', e)), - cache(5) - ); - - defer(() => a) - .pipe(delay(2)) - .subscribe(e => console.log(e)); - defer(() => a) - .pipe(delay(2)) - .subscribe(e => console.log(e)); - - await new Promise(done => setTimeout(done), 500); - - defer(() => a) - .pipe(delay(100)) - .subscribe(e => console.log(e)); - - await new Promise(done => setTimeout(done), 1000); - }); -}); - -describe('cache', () => { - beforeEach(() => { - let counter = 0; - fetchMock.mockGlobal().get( - 'https://httpbin.org/my-url-fast', - () => - new Response(++counter, { - status: 200, - headers: { 'Content-type': 'plain/text' } - }), - { delay: 0, repeat: 2 } - ); - }); - - afterEach(() => { - fetchMock.unmockGlobal(); - }); - - test('cache resetted after 100ms', async () => { - const a = of('https://httpbin.org/my-url-fast').pipe( - requestText(), - tap(() => console.log('CHECK')), - cache(1000) - ); - await new Promise(done => { - a.subscribe({ - next: e => expect(e).toBe('1'), - complete: () => done() - }); - }); - - await new Promise(done => { - a.subscribe({ - next: e => expect(e).toBe('1'), - complete: () => done() - }); - }); - - await new Promise(resolve => setTimeout(resolve, 1000)); - - await new Promise(done => { - a.subscribe({ - next: e => expect(e).toBe('2'), - complete: () => done() - }); - }); - }); }); diff --git a/packages/operators/src/request/concurrentRequest.test.js b/packages/operators/src/request/concurrentRequest.test.js index 7aa6ac6..227545f 100644 --- a/packages/operators/src/request/concurrentRequest.test.js +++ b/packages/operators/src/request/concurrentRequest.test.js @@ -1,74 +1,53 @@ -import { concatAll, concatMap, delay, from, map, of, tap, toArray } from 'rxjs'; +import { concatAll, delay, map, of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { mockAsync } from '../../../mock/async'; +import { mockResponse } from '../../../mock/response'; import { log, logResult } from '../log'; -import { resolveJSON } from './response'; +import { resolveJSON, resolveText } from './response'; -describe('concurrent request - mocked', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).to.eql(expected); - }); +describe('concurrent request', () => { + let testScheduler; - beforeEach(() => { - vi.doMock('./request', importOriginal => ({ - request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) - })); - }); + beforeAll(() => { + vi.spyOn(global, 'fetch').mockImplementation(({ v, t }) => mockAsync(v).pipe(delay(t))); - afterEach(() => { - vi.doUnmock('./request'); + global.Response = mockResponse(); }); - afterAll(function () { - vi.resetModules(); + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected)); }); - test('classic testing', async () => { - const { concurrentRequest } = await import('./concurrentRequest'); - - const triggerVal = [ - { t: 20, v: 'a' }, - { t: 50, v: 'b' }, - { t: 10, v: 'c' }, - { t: 30, v: 'd' }, - { t: 40, v: 'e' } - ]; - const sortedVal = [...triggerVal].sort((a, b) => a.t - b.t).map(({ v }) => v); - - await new Promise((done, error) => { - from(triggerVal) - .pipe(concurrentRequest(triggerVal.length), toArray()) - .subscribe({ - next: e => expect(e).toStrictEqual(sortedVal), - complete: () => done(), - error: e => error(e) - }); - }); + afterAll(function () { + vi.restoreAllMocks(); }); - test('marble testing', async () => { + test('default', async () => { const { concurrentRequest } = await import('./concurrentRequest'); const triggerVal = { - a: { t: 2, v: 'a' }, - b: { t: 5, v: 'b' }, - c: { t: 1, v: 'c' }, - d: { t: 3, v: 'd' }, - e: { t: 4, v: 'e' } + a: { t: 2, v: new Response('a') }, + b: { t: 5, v: new Response('b') }, + c: { t: 1, v: new Response('c') }, + d: { t: 3, v: new Response('d') }, + e: { t: 4, v: new Response('e') } }; - const expectedVal = Object.fromEntries(Object.entries(triggerVal).map(([k, { v }]) => [k, v])); testScheduler.run(({ cold, expectObservable }) => { expectObservable( - cold('-a-b-(cd)-e----', triggerVal).pipe(concurrentRequest(Object.keys(triggerVal).length)) - ).toBe('---a--c-(bd)--e', expectedVal); + cold('-a-b-(cd)-e----', triggerVal).pipe( + concurrentRequest(Object.keys(triggerVal).length), + resolveText() + ) + ).toBe('---a--c-(bd)--e'); }); }); }); describe('concurrent request - demo', () => { - test('sample testing', async () => { + test('sample', async () => { const { concurrentRequest } = await import('./concurrentRequest'); await logResult( diff --git a/packages/operators/src/request/lazyPagination.js b/packages/operators/src/request/lazyPagination.js index cc21be2..26a2558 100644 --- a/packages/operators/src/request/lazyPagination.js +++ b/packages/operators/src/request/lazyPagination.js @@ -1,11 +1,11 @@ -import { concatMap, map, tap } from 'rxjs'; +import { concatMap, map } from 'rxjs'; import { concurrentRequest } from './concurrentRequest'; export const lazyPagination = ({ pager, concurrent, resolveRoute }) => { return source => source.pipe( - concatMap(({ url }) => { + concatMap(url => { return pager.pipe( map(options => resolveRoute(url, options)), concurrentRequest(concurrent) diff --git a/packages/operators/src/request/lazyPagination.test.js b/packages/operators/src/request/lazyPagination.test.js index 5167ba5..3d3f428 100644 --- a/packages/operators/src/request/lazyPagination.test.js +++ b/packages/operators/src/request/lazyPagination.test.js @@ -1,38 +1,35 @@ -import { concatAll, concatMap, delay, map, of, Subject, tap } from 'rxjs'; +import { concatAll, delay, map, of, Subject, tap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { mockAsync } from '../../../mock/async'; +import { mockResponse } from '../../../mock/response'; import { log, logResult } from '../log'; +import { resolveJSON } from './response'; -describe('lazy pagination - mocked', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).to.eql(expected); - }); +describe('lazy pagination', () => { + let testScheduler; - beforeEach(() => { - vi.doMock('./request', importOriginal => ({ - request: () => source => source.pipe(concatMap(({ v, t }) => of(v).pipe(delay(t)))) - })); - }); + beforeAll(() => { + vi.spyOn(global, 'fetch').mockImplementation(({ v, t }) => mockAsync(v).pipe(delay(t))); - afterEach(() => { - vi.doUnmock('./request'); + global.Response = mockResponse(); }); - afterAll(() => { - vi.resetModules(); + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected)); }); - test('classic testing', () => { - // + afterAll(() => { + vi.restoreAllMocks(); }); - test('marble testing', async () => { + test('default', async () => { const { lazyPagination } = await import('./lazyPagination'); const pager = new Subject(); - const triggerValues = { + const triggerVal = { a: () => pager.next({ value: 'a' }), b: () => pager.next({ value: 'b' }), c: () => pager.next({ value: 'c' }), @@ -40,43 +37,47 @@ describe('lazy pagination - mocked', () => { e: () => pager.next({ value: 'e' }) }; - const responseValues = { - a: { t: 2, v: { value: 'a' } }, - b: { t: 5, v: { value: 'b' } }, - c: { t: 3, v: { value: 'c' } }, - d: { t: 1, v: { value: 'd' } }, - e: { t: 4, v: { value: 'e' } } + const expectedVal = { + a: { value: '1' }, + b: { value: '2' }, + c: { value: '3' }, + d: { value: '4' }, + e: { value: '5' } }; - const expectedValues = Object.fromEntries( - Object.entries(responseValues).map(([key, v]) => [key, v.v]) - ); + const responseVal = { + a: { t: 2, v: new Response(expectedVal.a) }, + b: { t: 5, v: new Response(expectedVal.b) }, + c: { t: 3, v: new Response(expectedVal.c) }, + d: { t: 1, v: new Response(expectedVal.d) }, + e: { t: 4, v: new Response(expectedVal.e) } + }; testScheduler.run(({ cold, expectObservable }) => { expectObservable( - of({ url: 'https://example.com' }).pipe( + of('https://example.com').pipe( lazyPagination({ pager, concurrent: 5, - resolveRoute: (url, { value }) => responseValues[String(value)] - }) + resolveRoute: (url, { value }) => responseVal[String(value)] + }), + resolveJSON() ) - ).toBe('--daceb--------', expectedValues); - expectObservable(cold('-(abcde)--------', triggerValues).pipe(tap(fn => fn()))); + ).toBe('--daceb--------', expectedVal); + expectObservable(cold('-(abcde)--------', triggerVal).pipe(tap(fn => fn()))); }); }); }); describe('lazy pagination - demo', () => { - test('sample testing', async () => { + test('sample', async () => { const { lazyPagination } = await import('./lazyPagination'); - const { resolveJSON } = await import('./response'); const pager = new Subject(); const result = logResult( 'demo', - of({ url: new URL('https://dummyjson.com/products') }).pipe( + of(new URL('https://dummyjson.com/products')).pipe( lazyPagination({ pager, concurrent: 4, diff --git a/packages/operators/src/request/polling.test.js b/packages/operators/src/request/polling.test.js index 825e7f3..49f3842 100644 --- a/packages/operators/src/request/polling.test.js +++ b/packages/operators/src/request/polling.test.js @@ -1,103 +1,58 @@ -import fetchMock from 'fetch-mock'; -import { concatMap, map, of, take, tap } from 'rxjs'; +import { concatMap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { createResponse } from '../../../test-utils/response'; -import { log } from '../log'; -import { resolveJSON } from './response'; +import { mockAsync } from '../../../mock/async'; +import { mockResponse } from '../../../mock/response'; -describe('polling - mocked', () => { - let triggerVal; - let expectedArrayBuffer; +describe('polling', () => { + let testScheduler; - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).to.eql(expected); + beforeAll(async () => { + global.Response = mockResponse(); }); - beforeEach(async () => { - triggerVal = [ - createResponse('https://example.com/', 'a'), - createResponse('https://example.com/', 'a'), - createResponse('https://example.com/', 'a'), - createResponse('https://example.com/', 'b'), - createResponse('https://example.com/', 'b'), - createResponse('https://example.com/', 'c'), - createResponse('https://example.com/', 'c') - ]; - - expectedArrayBuffer = await Promise.all(triggerVal.map(e => e.clone().arrayBuffer())); - - let counter = 0; - vi.doMock('./request', importOriginal => ({ - request: () => source => source.pipe(map(() => triggerVal[counter++])) - })); - - vi.spyOn(Response.prototype, 'arrayBuffer').mockImplementation(() => { - return of(expectedArrayBuffer[counter - 1]); - }); - }); - - afterEach(() => { - vi.doUnmock('./request'); + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected)); }); afterAll(() => { vi.restoreAllMocks(); - vi.resetModules(); }); - test('marble testing', async () => { + test('default', async () => { const { polling } = await import('./polling'); + const expectedVal = { + a: await new Blob(['a']).arrayBuffer(), + b: await new Blob(['a']).arrayBuffer(), + c: await new Blob(['a']).arrayBuffer(), + d: await new Blob(['b']).arrayBuffer(), + e: await new Blob(['b']).arrayBuffer(), + f: await new Blob(['c']).arrayBuffer(), + g: await new Blob(['c']).arrayBuffer() + }; + + const triggerVal = [ + new Response(expectedVal.a), + new Response(expectedVal.b), + new Response(expectedVal.c), + new Response(expectedVal.d), + new Response(expectedVal.e), + new Response(expectedVal.f), + new Response(expectedVal.g) + ]; + + vi.spyOn(global, 'fetch').mockImplementation(() => mockAsync(triggerVal.shift())); + testScheduler.run(({ cold, expectObservable }) => { - const stream = cold('a------------', { a: 'https://example.com/' }).pipe( + const stream = cold('a------------', { a: 'a' }).pipe( polling(2), concatMap(e => e.arrayBuffer()) ); const unsubA = '^------------!'; - expectObservable(stream, unsubA).toBe('a-----b---c--', { - a: expectedArrayBuffer[0], - b: expectedArrayBuffer[3], - c: expectedArrayBuffer[5] - }); - }); - }); -}); - -describe('classic testing', () => { - beforeEach(function () { - let counter = 0; - fetchMock.mockGlobal().get('https://httpbin.org/my-url-fast', () => { - if (counter++ < 2) { - return new Response(JSON.stringify({ hello: 'fast world' }), { - status: 200, - headers: { 'Content-type': 'application/json' } - }); - } - return new Response(JSON.stringify({ hello: 'faster world' }), { - status: 200, - headers: { 'Content-type': 'application/json' } - }); - }); - }); - - afterEach(() => { - fetchMock.unmockGlobal(); - }); - - test('auto polling', async () => { - const { polling } = await import('./polling'); - const expected = [{ hello: 'fast world' }, { hello: 'faster world' }]; - - return new Promise(done => { - of(new URL('https://httpbin.org/my-url-fast')) - .pipe(polling(), log(false), resolveJSON(), log(false), take(2)) - .subscribe({ - next: e => expect(e).deep.include(expected.shift()), - complete: () => done() - }); + expectObservable(stream, unsubA).toBe('a-----d---f--', expectedVal); }); }); }); diff --git a/packages/operators/src/request/request.js b/packages/operators/src/request/request.js index 123430b..bd9a992 100644 --- a/packages/operators/src/request/request.js +++ b/packages/operators/src/request/request.js @@ -1,30 +1,30 @@ -import { concatMap, throwError } from 'rxjs'; +import { concatMap, from, throwError } from 'rxjs'; import { resolveBlob, resolveJSON, resolveText } from './response'; import { networkRetry } from './retry'; -export const request = () => { +export const request = options => { return source => source.pipe( concatMap(req => { try { - return fetch(req); + return from(fetch(req)); } catch { return throwError(() => new Error('Failed to fetch: resource not valid')); } }), - networkRetry() + networkRetry(options) ); }; -export const requestJSON = () => { - return source => source.pipe(request(), resolveJSON()); +export const requestJSON = options => { + return source => source.pipe(request(options), resolveJSON()); }; -export const requestText = () => { - return source => source.pipe(request(), resolveText()); +export const requestText = options => { + return source => source.pipe(request(options), resolveText()); }; -export const requestBlob = () => { - return source => source.pipe(request(), resolveBlob()); +export const requestBlob = options => { + return source => source.pipe(request(options), resolveBlob()); }; diff --git a/packages/operators/src/request/request.test.js b/packages/operators/src/request/request.test.js index 295d367..c20739c 100644 --- a/packages/operators/src/request/request.test.js +++ b/packages/operators/src/request/request.test.js @@ -1,14 +1,132 @@ -import fetchMock from 'fetch-mock'; import { readFile } from 'node:fs/promises'; import { of } from 'rxjs'; -import { afterEach, test, describe, beforeEach, expect } from 'vitest'; +import { TestScheduler } from 'rxjs/testing'; +import { test, describe, beforeEach, expect, vi, afterAll, beforeAll } from 'vitest'; -import { log } from '../log.js'; -import { request, requestJSON } from './request.js'; +import { mockAsync } from '../../../mock/async.js'; +import { mockResponse } from '../../../mock/response.js'; +import { log, logResult } from '../log.js'; import { resolveJSON } from './response.js'; -describe('request observable with default ', () => { - test('successfull upload', async () => { +describe('request', () => { + let testScheduler; + + beforeAll(() => { + vi.spyOn(global, 'fetch').mockImplementation(v => mockAsync(v())); + + global.Response = mockResponse(); + }); + + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => expect(actual).deep.equal(expected)); + }); + + afterAll(() => { + vi.restoreAllMocks(); + }); + + test('dynamic timeout', async () => { + const { request } = await import('./request.js'); + + const expectedVal = { + a: new Error('NO CONNECTION'), + b: { ok: false }, + c: { ok: true } + }; + + const triggerVal = [ + () => { + throw expectedVal.a; + }, + () => expectedVal.b, + () => expectedVal.c + ]; + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', { a: () => triggerVal.shift()() }).pipe(request()); + expectObservable(stream).toBe('5000ms c|', expectedVal); + }); + }); + + test('static timeout', async () => { + const { request } = await import('./request.js'); + + const expectedVal = { + a: new Error('NO CONNECTION'), + b: { ok: false }, + c: { ok: true } + }; + + const triggerVal = [ + () => { + throw expectedVal.a; + }, + () => expectedVal.b, + () => expectedVal.c + ]; + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', { a: () => triggerVal.shift()() }).pipe( + request({ timeout: () => 5 }) + ); + expectObservable(stream).toBe('----------c|', expectedVal); + }); + }); + + test('resolveJSON', async () => { + const { requestJSON } = await import('./request.js'); + + const expectedVal = { + a: { hello: 'world' } + }; + const triggerVal = { + a: () => new Response(expectedVal.a) + }; + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', triggerVal).pipe(requestJSON()); + expectObservable(stream).toBe('a|', expectedVal); + }); + }); + + test('resolveText', async () => { + const { requestText } = await import('./request.js'); + + const expectedVal = { + a: 'hello world' + }; + const triggerVal = { + a: () => new Response(expectedVal.a) + }; + + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', triggerVal).pipe(requestText()); + expectObservable(stream).toBe('a|', expectedVal); + }); + }); + + test('resolveBlob', async () => { + const { requestBlob } = await import('./request.js'); + + const expectedVal = { + a: new Blob(['a'], { type: 'text/plain' }) + }; + const triggerVal = { + a: () => new Response(expectedVal.a) + }; + + // TODO: correctly compare blob - currently successful test, while blob content is different + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', triggerVal).pipe(requestBlob()); + expectObservable(stream).toBe('a|', expectedVal); + }); + }); +}); + +describe('request - demo ', () => { + test('sample - successfull upload', async () => { + const { request } = await import('./request.js'); + const formData = new FormData(); formData.set( 'file', @@ -27,78 +145,9 @@ describe('request observable with default ', () => { body: formData }); - return new Promise(done => { - of(req) - .pipe(request(), log(false), resolveJSON(), log(true)) - .subscribe({ - next: e => { - expect(e) - .deep.includes({ originalname: 'test_image.jpg' }) - .have.all.keys('filename', 'location'); - }, - complete: () => done() - }); - }); - }); -}); - -describe('request observable with default operators', () => { - beforeEach(() => { - let counter = 0; - fetchMock.mockGlobal().get( - 'https://httpbin.org/my-url-fast', - () => { - return new Response(JSON.stringify({ hello: 'fast world' }), { - status: ++counter > 2 ? 200 : 404, - headers: { 'Content-type': 'application/json' } - }); - }, - { delay: 0, repeat: 4 } - ); - - fetchMock.get( - 'https://awesome.mock/delayed-response', - () => { - return new Response(JSON.stringify({ hello: 'fast world' }), { - status: 200, - headers: { 'Content-type': 'application/json' } - }); - }, - { delay: 1000 } + await logResult( + 'demo', + of(req).pipe(log('request:upload'), request(), log('request:upload:response'), resolveJSON()) ); }); - - afterEach(() => { - fetchMock.unmockGlobal(); - }); - - test('successfull request', () => - new Promise(done => { - of('https://httpbin.org/my-url-fast') - .pipe(request(), log(false)) - .subscribe({ - next: resp => expect(resp).deep.includes({ ok: true }), - complete: () => done() - }); - })); - - test('successfull request - indirect json resolve', () => - new Promise(done => { - of('https://awesome.mock/delayed-response') - .pipe(request(), log(false), resolveJSON(), log(false)) - .subscribe({ - next: data => expect(data).deep.equal({ hello: 'fast world' }), - complete: () => done() - }); - })); - - test('successfull request - direct json resolve', () => - new Promise(done => { - of('https://awesome.mock/delayed-response') - .pipe(requestJSON(), log(false)) - .subscribe({ - next: data => expect(data).deep.equal({ hello: 'fast world' }), - complete: () => done() - }); - })); }); diff --git a/packages/operators/src/request/response.js b/packages/operators/src/request/response.js index eef7390..f03a814 100644 --- a/packages/operators/src/request/response.js +++ b/packages/operators/src/request/response.js @@ -2,7 +2,7 @@ import { shallowEqual } from 'fast-equals'; import { combineLatest, concatMap, distinctUntilChanged, from, map, of } from 'rxjs'; export const resolve = (type = 'json') => { - return source => source.pipe(concatMap(e => e[String(type)]())); + return source => source.pipe(concatMap(e => from(e[String(type)]()))); }; export const resolveJSON = () => { diff --git a/packages/operators/src/request/response.test.js b/packages/operators/src/request/response.test.js index 54e9d26..d5e1ffb 100644 --- a/packages/operators/src/request/response.test.js +++ b/packages/operators/src/request/response.test.js @@ -1,112 +1,85 @@ -import { map, of, tap } from 'rxjs'; +import { concatMap } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; -import { afterEach, test, describe, beforeEach, expect, vi } from 'vitest'; +import { afterEach, test, describe, beforeEach, expect, vi, beforeAll } from 'vitest'; -import { createResponse } from '../../../test-utils/response'; +import { mockResponse } from '../../../mock/response'; import { log } from '../log'; import { distinctUntilResponseChanged, resolveJSON, resolveText } from './response'; describe('response', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).to.eql(expected); + let testScheduler; + + beforeAll(() => { + global.Response = mockResponse(); }); beforeEach(() => { - // + testScheduler = new TestScheduler((actual, expected) => expect(actual).to.eql(expected)); }); afterEach(() => { vi.restoreAllMocks(); }); - test('resolve json', () => { - return new Promise(done => { - of(new Response(JSON.stringify({ hello: 'world' }))) - .pipe(resolveJSON(), log(false)) - .subscribe({ - next: e => expect(e).includes({ hello: 'world' }), - complete: () => done() - }); - }); - }); + test('resolveJSON', () => { + const expectedVal = { + a: { hello: 'world' } + }; + const triggerVal = { + a: new Response(expectedVal.a) + }; - test('resolve text', () => { - return new Promise(done => { - of(new Response('hello world')) - .pipe(resolveText(), log(false)) - .subscribe({ - next: e => expect(e).toBe('hello world'), - complete: () => done() - }); + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', triggerVal).pipe(resolveJSON()); + expectObservable(stream).toBe('a|', expectedVal); }); }); - test('emit only changed responses', () => { - const triggerValues = [ - new Response('a'), - new Response('a'), - new Response('b'), - new Response('b'), - new Response('c'), - new Response('a'), - new Response('a'), - new Response('b') - ]; - const expectedValues = ['a', 'b', 'c', 'a', 'b']; + test('resolveText', () => { + const expectedVal = { + a: 'hello world' + }; + const triggerVal = { + a: new Response(expectedVal.a) + }; - return new Promise(done => { - of(...triggerValues) - .pipe(distinctUntilResponseChanged(), log(false), resolveText(), log(false)) - .subscribe({ - next: e => expect(e).toBe(expectedValues.shift()), - complete: () => done() - }); + testScheduler.run(({ cold, expectObservable }) => { + const stream = cold('a|', triggerVal).pipe(resolveText()); + expectObservable(stream).toBe('a|', expectedVal); }); }); - test('marble testing', async () => { - const triggerValues = { - a: createResponse('/a', 'a'), - b: createResponse('/b', 'a'), - c: createResponse('/c', 'b'), - d: createResponse('/d', 'b'), - e: createResponse('/e', 'c'), - f: createResponse('/f', 'a'), - g: createResponse('/g', 'a'), - h: createResponse('/h', 'b') + test('filtered by response change', async () => { + const expectedVal = { + a: await new Blob(['a']).arrayBuffer(), + b: await new Blob(['a']).arrayBuffer(), + c: await new Blob(['b']).arrayBuffer(), + d: await new Blob(['b']).arrayBuffer(), + e: await new Blob(['c']).arrayBuffer(), + f: await new Blob(['a']).arrayBuffer(), + g: await new Blob(['a']).arrayBuffer(), + h: await new Blob(['b']).arrayBuffer() }; - const expectedValues = Object.fromEntries( - await Promise.all( - Object.entries(triggerValues).map(async ([key, resp]) => { - return [`/${key}`, await resp.clone().arrayBuffer()]; - }) - ) - ); - - vi.spyOn(Response.prototype, 'arrayBuffer').mockImplementation(function (e) { - return [expectedValues[this.url]]; - }); + const triggerValues = { + a: new Response(expectedVal.a, '/a'), + b: new Response(expectedVal.b, '/b'), + c: new Response(expectedVal.c, '/c'), + d: new Response(expectedVal.d, '/d'), + e: new Response(expectedVal.e, '/e'), + f: new Response(expectedVal.f, '/f'), + g: new Response(expectedVal.g, '/g'), + h: new Response(expectedVal.h, '/h') + }; testScheduler.run(({ cold, expectObservable }) => { expectObservable( - cold('-a-b-c-d-e-f-g-h-', triggerValues).pipe( + cold('-a-b-c-d-e-f-g-h-|', triggerValues).pipe( distinctUntilResponseChanged(), - map(resp => resp.arrayBuffer()) + concatMap(resp => resp.arrayBuffer()), + log('marble:result') ) - ).toBe('-a---c---e-f---h-', { - a: triggerValues.a.arrayBuffer(), - c: triggerValues.c.arrayBuffer(), - e: triggerValues.e.arrayBuffer(), - f: triggerValues.f.arrayBuffer(), - h: triggerValues.h.arrayBuffer() - }); + ).toBe('-a---c---e-f---h-|', expectedVal); }); }); }); - -const pick = (obj, arr) => - arr.reduce( - (acc, record) => (record in obj && (acc[String(record)] = obj[String(record)]), acc), - {} - ); diff --git a/packages/operators/src/request/retry.js b/packages/operators/src/request/retry.js index e176618..cabe408 100644 --- a/packages/operators/src/request/retry.js +++ b/packages/operators/src/request/retry.js @@ -1,11 +1,12 @@ import { - catchError, combineLatest, concatMap, delay, filter, map, + merge, of, + partition, retry, tap, throwError @@ -20,7 +21,13 @@ export const networkRetry = ({ timeout = defaultTimeout, count } = {}) => { return source => { return source.pipe( - concatMap(resp => (!resp.ok && throwError(() => new Error('invalid request'))) || of(resp)), + concatMap(resp => { + const [success, error] = partition(of(resp), resp => resp.ok); + return merge( + success, + error.pipe(concatMap(() => throwError(() => new Error('invalid request')))) + ); + }), retry({ count, delay: () => determineDelayWhenOnline(timeout, ++counter) @@ -38,6 +45,6 @@ const determineDelayWhenOnline = (timeout, counter) => { // continue only if all observables are valid filter(valid => valid), tap(() => console.log(`retry: request - next: ${counter} in ${timeout(counter)}ms`)), - delay(timeout(counter) || timeout) + delay(timeout(counter)) ); }; diff --git a/packages/operators/src/request/retry.test.js b/packages/operators/src/request/retry.test.js index 4b8dd0d..62b8760 100644 --- a/packages/operators/src/request/retry.test.js +++ b/packages/operators/src/request/retry.test.js @@ -2,26 +2,21 @@ import { map } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { afterEach, beforeEach, describe, expect, test } from 'vitest'; +import { log } from '../log'; import { networkRetry } from './retry'; describe('request retry', () => { - const testScheduler = new TestScheduler((actual, expected) => { - expect(actual).deep.equal(expected); - }); + let testScheduler; beforeEach(() => { - // + testScheduler = new TestScheduler((actual, expected) => expect(actual).deep.equal(expected)); }); afterEach(() => { // }); - test('classic testing', () => { - // - }); - - test('marble testing', () => { + test('2x error -> 1x success', () => { const error = new Response('', { status: 500 }); const success = new Response('a', { status: 200 }); const orderedResponses = [error, error, success]; @@ -29,15 +24,16 @@ describe('request retry', () => { testScheduler.run(({ cold, expectObservable }) => { // retry is repeating the sequence // if you define a delay, you have to add the delay to the subscribe multiple times (num retries) - const stream = cold('a----------', { + const stream = cold('a|', { a: () => orderedResponses.shift() }).pipe( map(fn => fn()), - networkRetry({ timeout: () => 5 }) + networkRetry({ timeout: () => 5 }), + log('marble:result') ); - const unsubA = '^----------!'; - expectObservable(stream, unsubA).toBe('----------a', { a: success }, new Error()); + const unsubA = '^-----------'; + expectObservable(stream, unsubA).toBe('----------a|', { a: success }); }); }); }); diff --git a/packages/test-utils/response.js b/packages/test-utils/response.js deleted file mode 100644 index 04d6388..0000000 --- a/packages/test-utils/response.js +++ /dev/null @@ -1,5 +0,0 @@ -export const createResponse = (url, content, options = { status: 200 }) => { - const resp = new Response(content, options); - Object.defineProperty(resp, 'url', { value: url }); - return resp; -}; diff --git a/packages/test-utils/utils.js b/packages/test-utils/utils.js deleted file mode 100644 index e69de29..0000000 From fe7d78f638c14087767227ff2307563f8e33bb2e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 24 Nov 2024 20:06:08 +0000 Subject: [PATCH 11/17] chore(release): 1.0.6-beta.2 [skip ci] # [@rxjs-collection/operators-v1.0.6-beta.2](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.6-beta.1...@rxjs-collection/operators-v1.0.6-beta.2) (2024-11-24) ### Bug Fixes * **operators:** cleanup and standardize tests ([aabbbd8](https://github.com/basics/rxjs-collection/commit/aabbbd89bd76ca57cfd20b747890fcc74ee01227)) * **operators:** marble testing ([2b5f78b](https://github.com/basics/rxjs-collection/commit/2b5f78bacbbae30ab02f62d1e603816cadba03ee)) --- packages/operators/CHANGELOG.md | 8 ++++++++ packages/operators/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/operators/CHANGELOG.md b/packages/operators/CHANGELOG.md index 297e4e7..648eb77 100644 --- a/packages/operators/CHANGELOG.md +++ b/packages/operators/CHANGELOG.md @@ -1,5 +1,13 @@ # Project Changelog +# [@rxjs-collection/operators-v1.0.6-beta.2](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.6-beta.1...@rxjs-collection/operators-v1.0.6-beta.2) (2024-11-24) + + +### Bug Fixes + +* **operators:** cleanup and standardize tests ([aabbbd8](https://github.com/basics/rxjs-collection/commit/aabbbd89bd76ca57cfd20b747890fcc74ee01227)) +* **operators:** marble testing ([2b5f78b](https://github.com/basics/rxjs-collection/commit/2b5f78bacbbae30ab02f62d1e603816cadba03ee)) + # [@rxjs-collection/operators-v1.0.6-beta.1](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.5...@rxjs-collection/operators-v1.0.6-beta.1) (2024-11-14) diff --git a/packages/operators/package.json b/packages/operators/package.json index fcf81e5..e598ed3 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -1,6 +1,6 @@ { "name": "@rxjs-collection/operators", - "version": "1.0.6-beta.1", + "version": "1.0.6-beta.2", "description": "rxjs operators", "license": "MIT", "contributors": [ From 2949edb254b878b5908903f813374deb8ffe4842 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 24 Nov 2024 20:06:20 +0000 Subject: [PATCH 12/17] chore(release): 1.0.5-beta.1 [skip ci] # [@rxjs-collection/observables-v1.0.5-beta.1](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/observables-v1.0.4...@rxjs-collection/observables-v1.0.5-beta.1) (2024-11-24) ### Bug Fixes * **operators:** cleanup and standardize tests ([aabbbd8](https://github.com/basics/rxjs-collection/commit/aabbbd89bd76ca57cfd20b747890fcc74ee01227)) --- packages/observables/CHANGELOG.md | 7 +++++++ packages/observables/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/observables/CHANGELOG.md b/packages/observables/CHANGELOG.md index 91b1559..56d83a3 100644 --- a/packages/observables/CHANGELOG.md +++ b/packages/observables/CHANGELOG.md @@ -1,5 +1,12 @@ # Project Changelog +# [@rxjs-collection/observables-v1.0.5-beta.1](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/observables-v1.0.4...@rxjs-collection/observables-v1.0.5-beta.1) (2024-11-24) + + +### Bug Fixes + +* **operators:** cleanup and standardize tests ([aabbbd8](https://github.com/basics/rxjs-collection/commit/aabbbd89bd76ca57cfd20b747890fcc74ee01227)) + # [@rxjs-collection/observables-v1.0.4](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/observables-v1.0.3...@rxjs-collection/observables-v1.0.4) (2024-10-24) diff --git a/packages/observables/package.json b/packages/observables/package.json index 05c2415..49d1813 100644 --- a/packages/observables/package.json +++ b/packages/observables/package.json @@ -1,6 +1,6 @@ { "name": "@rxjs-collection/observables", - "version": "1.0.4", + "version": "1.0.5-beta.1", "description": "rxjs observables", "license": "MIT", "contributors": [ From fbaaf8ae9723e6960782b5cd96c2be5ce38a170c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 20:07:35 +0000 Subject: [PATCH 13/17] fix(deps): pin dependencies --- package-lock.json | 10 ++++++---- packages/operators/package.json | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3067058..13ca13a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2631,6 +2631,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3310,6 +3311,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -12605,7 +12607,7 @@ }, "packages/observables": { "name": "@rxjs-collection/observables", - "version": "1.0.4", + "version": "1.0.5-beta.1", "license": "MIT", "dependencies": { "@rxjs-collection/operators": "*", @@ -12614,12 +12616,12 @@ }, "packages/operators": { "name": "@rxjs-collection/operators", - "version": "1.0.6-beta.1", + "version": "1.0.6-beta.2", "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*", - "ansi-colors": "^4.1.3", - "consola": "^3.2.3", + "ansi-colors": "4.1.3", + "consola": "3.2.3", "fast-equals": "5.0.1", "rxjs": "7.8.1" }, diff --git a/packages/operators/package.json b/packages/operators/package.json index e598ed3..cead04b 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "@rxjs-collection/observables": "*", - "ansi-colors": "^4.1.3", - "consola": "^3.2.3", + "ansi-colors": "4.1.3", + "consola": "3.2.3", "fast-equals": "5.0.1", "rxjs": "7.8.1" }, From df07d8f8138cf464ad2f1b2a6fee54c236eca185 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 20:09:00 +0000 Subject: [PATCH 14/17] chore(deps): update all non-major dependencies --- package-lock.json | 132 ++++++++++++++++++++++------------------------ package.json | 10 ++-- 2 files changed, 67 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 13ca13a..f89dd28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "packages/observables" ], "devDependencies": { - "@commitlint/config-conventional": "19.5.0", + "@commitlint/config-conventional": "19.6.0", "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", @@ -22,16 +22,16 @@ "@semantic-release/release-notes-generator": "14.0.1", "@vitest/coverage-v8": "2.1.5", "@vitest/eslint-plugin": "1.1.10", - "commitlint": "19.5.0", - "eslint": "9.14.0", + "commitlint": "19.6.0", + "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-no-secrets": "1.1.2", "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", - "fetch-mock": "12.1.0", + "fetch-mock": "12.2.0", "happy-dom": "15.11.6", - "husky": "9.1.6", + "husky": "9.1.7", "lint-staged": "15.2.10", "prettier": "3.3.3", "semantic-release": "24.2.0", @@ -214,14 +214,14 @@ } }, "node_modules/@commitlint/cli": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.5.0.tgz", - "integrity": "sha512-gaGqSliGwB86MDmAAKAtV9SV1SHdmN8pnGq4EJU4+hLisQ7IFfx4jvU4s+pk6tl0+9bv6yT+CaZkufOinkSJIQ==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.6.0.tgz", + "integrity": "sha512-v17BgGD9w5KnthaKxXnEg6KLq6DYiAxyiN44TpiRtqyW8NSq+Kx99mkEG8Qo6uu6cI5eMzMojW2muJxjmPnF8w==", "dev": true, "license": "MIT", "dependencies": { "@commitlint/format": "^19.5.0", - "@commitlint/lint": "^19.5.0", + "@commitlint/lint": "^19.6.0", "@commitlint/load": "^19.5.0", "@commitlint/read": "^19.5.0", "@commitlint/types": "^19.5.0", @@ -236,9 +236,9 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.5.0.tgz", - "integrity": "sha512-OBhdtJyHNPryZKg0fFpZNOBM1ZDbntMvqMuSmpfyP86XSfwzGw4CaoYRG4RutUPg0BTK07VMRIkNJT6wi2zthg==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.6.0.tgz", + "integrity": "sha512-DJT40iMnTYtBtUfw9ApbsLZFke1zKh6llITVJ+x9mtpHD08gsNXaIRqHTmwTZL3dNX5+WoyK7pCN/5zswvkBCQ==", "dev": true, "license": "MIT", "dependencies": { @@ -306,9 +306,9 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.5.0.tgz", - "integrity": "sha512-0XQ7Llsf9iL/ANtwyZ6G0NGp5Y3EQ8eDQSxv/SRcfJ0awlBY4tHFAvwWbw66FVUaWICH7iE5en+FD9TQsokZ5w==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.6.0.tgz", + "integrity": "sha512-Ov6iBgxJQFR9koOupDPHvcHU9keFupDgtB3lObdEZDroiG4jj1rzky60fbQozFKVYRTUdrBGICHG0YVmRuAJmw==", "dev": true, "license": "MIT", "dependencies": { @@ -320,15 +320,15 @@ } }, "node_modules/@commitlint/lint": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.5.0.tgz", - "integrity": "sha512-cAAQwJcRtiBxQWO0eprrAbOurtJz8U6MgYqLz+p9kLElirzSCc0vGMcyCaA1O7AqBuxo11l1XsY3FhOFowLAAg==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.6.0.tgz", + "integrity": "sha512-LRo7zDkXtcIrpco9RnfhOKeg8PAnE3oDDoalnrVU/EVaKHYBWYL1DlRR7+3AWn0JiBqD8yKOfetVxJGdEtZ0tg==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^19.5.0", + "@commitlint/is-ignored": "^19.6.0", "@commitlint/parse": "^19.5.0", - "@commitlint/rules": "^19.5.0", + "@commitlint/rules": "^19.6.0", "@commitlint/types": "^19.5.0" }, "engines": { @@ -463,9 +463,9 @@ } }, "node_modules/@commitlint/rules": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.5.0.tgz", - "integrity": "sha512-hDW5TPyf/h1/EufSHEKSp6Hs+YVsDMHazfJ2azIk9tHPXS6UqSz1dIRs1gpqS3eMXgtkT7JH6TW4IShdqOwhAw==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.6.0.tgz", + "integrity": "sha512-1f2reW7lbrI0X0ozZMesS/WZxgPa4/wi56vFuJENBmed6mWq5KsheN/nxqnl/C23ioxpPO/PL6tXpiiFy5Bhjw==", "dev": true, "license": "MIT", "dependencies": { @@ -946,9 +946,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -961,9 +961,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -971,9 +971,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1019,9 +1019,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", "dev": true, "license": "MIT", "engines": { @@ -1039,9 +1039,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3255,13 +3255,13 @@ } }, "node_modules/commitlint": { - "version": "19.5.0", - "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-19.5.0.tgz", - "integrity": "sha512-lCtwxgFulvMnCgBc8MVPlVMf+PNOqQSBhHpEnjV2JjEQEAhxjVDtC7IeuEtR+hHpGvHt6CwlLtm3uZNyEby5dQ==", + "version": "19.6.0", + "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-19.6.0.tgz", + "integrity": "sha512-0gOMRBSpnCw3Su0rfVeDqCe4ck/fkhGGC9UxVDeSyyCemFXs4U3BDuwMWvYcw4qsEAkPuDjQNoU8KWyPtHBq/w==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/cli": "^19.5.0", + "@commitlint/cli": "^19.6.0", "@commitlint/types": "^19.5.0" }, "bin": { @@ -3454,9 +3454,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -4040,27 +4040,27 @@ } }, "node_modules/eslint": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", - "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", + "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.14.0", - "@eslint/plugin-kit": "^0.2.0", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.0", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.5", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -4079,8 +4079,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -4697,9 +4696,9 @@ } }, "node_modules/fetch-mock": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.1.0.tgz", - "integrity": "sha512-xKcKpS9JIfA/vtYRGZtNXIYVHM7Ta/06e6tClSrJoOlU/46pribn1wmW2lDZkqY6MtloUSpeRzy+TbwSKxcvmQ==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.2.0.tgz", + "integrity": "sha512-XjgxM582kB0SzPOqH2UdGTwSqga8A8aBPjxcYr0wTeOlCWpZoK6zBrPzltECUTu6Zt3VTWafmKF599LN9BRN5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5350,9 +5349,9 @@ } }, "node_modules/husky": { - "version": "9.1.6", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", - "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -11644,13 +11643,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", diff --git a/package.json b/package.json index c192ce1..dcc166a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "packages/observables" ], "devDependencies": { - "@commitlint/config-conventional": "19.5.0", + "@commitlint/config-conventional": "19.6.0", "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", @@ -36,17 +36,17 @@ "@semantic-release/npm": "12.0.1", "@semantic-release/release-notes-generator": "14.0.1", "@vitest/coverage-v8": "2.1.5", - "commitlint": "19.5.0", - "eslint": "9.14.0", + "commitlint": "19.6.0", + "eslint": "9.15.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-no-secrets": "1.1.2", "eslint-plugin-perfectionist": "3.9.1", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "@vitest/eslint-plugin": "1.1.10", - "fetch-mock": "12.1.0", + "fetch-mock": "12.2.0", "happy-dom": "15.11.6", - "husky": "9.1.6", + "husky": "9.1.7", "lint-staged": "15.2.10", "prettier": "3.3.3", "semantic-release": "24.2.0", From 86eb77442269342123a2e213cc4063965e1ce805 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 20:09:15 +0000 Subject: [PATCH 15/17] chore(deps): update dependency eslint-plugin-perfectionist to v4 --- package-lock.json | 228 +++++++++++++++++----------------------------- package.json | 2 +- 2 files changed, 83 insertions(+), 147 deletions(-) diff --git a/package-lock.json b/package-lock.json index 13ca13a..382abcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-no-secrets": "1.1.2", - "eslint-plugin-perfectionist": "3.9.1", + "eslint-plugin-perfectionist": "4.0.3", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "fetch-mock": "12.1.0", @@ -2253,38 +2253,15 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/utils": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", - "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.10.0", - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/typescript-estree": "8.10.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", - "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", + "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0" + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2294,10 +2271,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", + "node_modules/@typescript-eslint/types": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", + "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", "dev": true, "license": "MIT", "engines": { @@ -2308,15 +2285,15 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", - "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", + "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "@typescript-eslint/visitor-keys": "8.10.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2337,61 +2314,76 @@ } } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", - "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.10.0", - "eslint-visitor-keys": "^3.4.3" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", + "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", + "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@typescript-eslint/types": "8.15.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@vitest/coverage-v8": { @@ -4128,80 +4120,21 @@ } }, "node_modules/eslint-plugin-perfectionist": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.9.1.tgz", - "integrity": "sha512-9WRzf6XaAxF4Oi5t/3TqKP5zUjERhasHmLFHin2Yw6ZAp/EP/EVA2dr3BhQrrHWCm5SzTMZf0FcjDnBkO2xFkA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-4.0.3.tgz", + "integrity": "sha512-CyafnreF6boy4lf1XaF72U8NbkwrfjU/mOf1y6doaDMS9zGXhUU1DSk+ZPf/rVwCf1PL1m+rhHqFs+IcB8kDmA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.9.0", - "@typescript-eslint/utils": "^8.9.0", - "minimatch": "^9.0.5", - "natural-compare-lite": "^1.4.0" + "@typescript-eslint/types": "^8.15.0", + "@typescript-eslint/utils": "^8.15.0", + "natural-orderby": "^5.0.0" }, "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "astro-eslint-parser": "^1.0.2", - "eslint": ">=8.0.0", - "svelte": ">=3.0.0", - "svelte-eslint-parser": "^0.41.1", - "vue-eslint-parser": ">=9.0.0" - }, - "peerDependenciesMeta": { - "astro-eslint-parser": { - "optional": true - }, - "svelte": { - "optional": true - }, - "svelte-eslint-parser": { - "optional": true - }, - "vue-eslint-parser": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/types": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", - "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-perfectionist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-perfectionist/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "eslint": ">=8.0.0" } }, "node_modules/eslint-plugin-prettier": { @@ -6718,12 +6651,15 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "node_modules/natural-orderby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz", + "integrity": "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/neo-async": { "version": "2.6.2", @@ -11843,9 +11779,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.1.tgz", + "integrity": "sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index c192ce1..04e1f4f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint": "9.14.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-no-secrets": "1.1.2", - "eslint-plugin-perfectionist": "3.9.1", + "eslint-plugin-perfectionist": "4.0.3", "eslint-plugin-prettier": "5.2.1", "eslint-plugin-security": "3.0.1", "@vitest/eslint-plugin": "1.1.10", From 8d6118d0d73b75996e1ccca57875c7424e0c0e58 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 24 Nov 2024 20:09:18 +0000 Subject: [PATCH 16/17] chore(release): 1.0.6-beta.3 [skip ci] # [@rxjs-collection/operators-v1.0.6-beta.3](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.6-beta.2...@rxjs-collection/operators-v1.0.6-beta.3) (2024-11-24) ### Bug Fixes * **deps:** pin dependencies ([fbaaf8a](https://github.com/basics/rxjs-collection/commit/fbaaf8ae9723e6960782b5cd96c2be5ce38a170c)) --- packages/operators/CHANGELOG.md | 7 +++++++ packages/operators/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/operators/CHANGELOG.md b/packages/operators/CHANGELOG.md index 648eb77..554e598 100644 --- a/packages/operators/CHANGELOG.md +++ b/packages/operators/CHANGELOG.md @@ -1,5 +1,12 @@ # Project Changelog +# [@rxjs-collection/operators-v1.0.6-beta.3](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.6-beta.2...@rxjs-collection/operators-v1.0.6-beta.3) (2024-11-24) + + +### Bug Fixes + +* **deps:** pin dependencies ([fbaaf8a](https://github.com/basics/rxjs-collection/commit/fbaaf8ae9723e6960782b5cd96c2be5ce38a170c)) + # [@rxjs-collection/operators-v1.0.6-beta.2](https://github.com/basics/rxjs-collection/compare/@rxjs-collection/operators-v1.0.6-beta.1...@rxjs-collection/operators-v1.0.6-beta.2) (2024-11-24) diff --git a/packages/operators/package.json b/packages/operators/package.json index cead04b..123b240 100644 --- a/packages/operators/package.json +++ b/packages/operators/package.json @@ -1,6 +1,6 @@ { "name": "@rxjs-collection/operators", - "version": "1.0.6-beta.2", + "version": "1.0.6-beta.3", "description": "rxjs operators", "license": "MIT", "contributors": [ From 14f4899b70c7bf86c84defbaa78a11caa03422ad Mon Sep 17 00:00:00 2001 From: StephanGerbeth Date: Sun, 24 Nov 2024 21:19:12 +0100 Subject: [PATCH 17/17] fix(eslint): perfectionist update --- eslint.config.js | 6 +++--- package-lock.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 0e28f5e..db11960 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,4 +1,6 @@ import js from '@eslint/js'; +// https://github.com/vitest-dev/eslint-plugin-vitest +import vitest from '@vitest/eslint-plugin'; // https://github.com/nickdeis/eslint-plugin-no-secrets import noSecrets from 'eslint-plugin-no-secrets'; // https://github.com/azat-io/eslint-plugin-perfectionist @@ -7,8 +9,6 @@ import perfectionist from 'eslint-plugin-perfectionist'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; // https://github.com/eslint-community/eslint-plugin-security import eslintPluginSecurity from 'eslint-plugin-security'; -// https://github.com/vitest-dev/eslint-plugin-vitest -import vitest from '@vitest/eslint-plugin'; import globals from 'globals'; import eslintIgnores from './eslint.ignores.js'; @@ -48,7 +48,7 @@ export default [ type: 'alphabetical', order: 'asc', ignoreCase: true, - internalPattern: ['~/**'], + internalPattern: ['^~/.*'], newlinesBetween: 'always', maxLineLength: undefined, groups: [ diff --git a/package-lock.json b/package-lock.json index cebfe05..abf4cc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12544,7 +12544,7 @@ }, "packages/operators": { "name": "@rxjs-collection/operators", - "version": "1.0.6-beta.2", + "version": "1.0.6-beta.3", "license": "MIT", "dependencies": { "@rxjs-collection/observables": "*",