Skip to content

Commit

Permalink
update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kilbot committed Jul 4, 2024
1 parent f1fcb81 commit 9d6f449
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 38 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"observable-hooks": "^4.2.3",
"rxdb": "15.24.0",
"rxjs": "^7.8.1",
"typescript": "^5.4.5"
"uuid": "10.0.0"
},
"devDependencies": {
"@babel/core": "^7.24.6",
Expand All @@ -37,6 +37,8 @@
"babel-jest": "^29.7.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"ts-jest": "^29.1.5"
"rxjs-marbles": "^7.0.1",
"ts-jest": "^29.1.5",
"typescript": "^5.4.5"
}
}
3 changes: 2 additions & 1 deletion src/query-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
combineLatest,
catchError,
} from 'rxjs';
import { map, switchMap, distinctUntilChanged, debounceTime, tap } from 'rxjs/operators';
import { map, switchMap, distinctUntilChanged, debounceTime, tap, startWith } from 'rxjs/operators';

import { SubscribableBase } from './subscribable-base';

Expand Down Expand Up @@ -144,6 +144,7 @@ export class Query<T extends RxCollection> extends SubscribableBase {
this.find$
.pipe(
distinctUntilChanged((prev, next) => {
console.log('DistinctUntilChanged', prev, next);
// Check if search is active and searchTerm has changed
if (prev.searchActive !== next.searchActive || prev.searchTerm !== next.searchTerm) {
return false;
Expand Down
65 changes: 58 additions & 7 deletions tests/__mocks__/http.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
const httpClientMock = {
get: jest.fn().mockResolvedValue({ data: {} }),
post: jest.fn().mockResolvedValue({ data: {} }),
put: jest.fn().mockResolvedValue({ data: {} }),
delete: jest.fn().mockResolvedValue({ data: {} }),
// ... other methods that your httpClient might have
import { AxiosRequestConfig } from 'axios';
import { Mock } from 'jest-mock';

interface MockResponse {
[key: string]: any;
}

interface HttpClientMock {
get: Mock<any, [string, AxiosRequestConfig?]>;
post: Mock<any, [string, any, AxiosRequestConfig?]>;
put: Mock<any, [string, any, AxiosRequestConfig?]>;
delete: Mock<any, [string, AxiosRequestConfig?]>;
__setMockResponse: (method: HttpMethod, url: string, params: Record<string, any>, response: any) => void;
__resetMockResponses: () => void;
}

type HttpMethod = 'get' | 'post' | 'put' | 'delete';

const mockResponses: Record<string, MockResponse> = {};

const httpClientMock: HttpClientMock = {
get: jest.fn((url: string, config?: AxiosRequestConfig) => {
return resolveResponse('get', url, config);
}),
post: jest.fn((url: string, data: any, config?: AxiosRequestConfig) => {
return resolveResponse('post', url, config, data);
}),
put: jest.fn((url: string, data: any, config?: AxiosRequestConfig) => {
return resolveResponse('put', url, config, data);
}),
delete: jest.fn((url: string, config?: AxiosRequestConfig) => {
return resolveResponse('delete', url, config);
}),
__setMockResponse: (method: HttpMethod, url: string, params: Record<string, any>, response: any) => {
const key = `${method}:${url}:${JSON.stringify(params) || ''}`;
mockResponses[key] = response;
},
__resetMockResponses: () => {
Object.keys(mockResponses).forEach(key => delete mockResponses[key]);
},
};

const standardErrorResponses = {
unauthorized: { status: 401, data: { message: 'Not authorized' } },
serverError: { status: 500, data: { message: 'Internal server error' } },
badRequest: { status: 400, data: { message: 'Bad request' } },
};

export default httpClientMock;
function resolveResponse(method: HttpMethod, url: string, config?: AxiosRequestConfig, data?: any) {
const key = `${method}:${url}:${JSON.stringify(config?.params) || ''}`;
if (mockResponses[key]) {
return Promise.resolve({ data: mockResponses[key] });
} else if (mockResponses[method] && mockResponses[method][url]) {
return Promise.resolve({ data: mockResponses[method][url] });
} else {
return Promise.resolve({ data: [] }); // Default response is an empty array
}
}

export { httpClientMock, standardErrorResponses };
2 changes: 2 additions & 0 deletions tests/helpers/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'rxdb';
// import { RxDBDevModePlugin } from 'rxdb/plugins/dev-mode';
import { getRxStorageMemory } from 'rxdb/plugins/storage-memory';
import { RxDBGenerateIdPlugin } from './generate-id';

import { logsLiteral } from './schemas/logs';
import { productsLiteral } from './schemas/products';
Expand All @@ -17,6 +18,7 @@ import { variationsLiteral } from './schemas/variations';
import type { RxCollectionCreator, RxCollection, RxDocument } from 'rxdb';

// addRxPlugin(RxDBDevModePlugin);
addRxPlugin(RxDBGenerateIdPlugin);

/**
* Products
Expand Down
57 changes: 57 additions & 0 deletions tests/helpers/generate-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { RxCollection, RxPlugin } from 'rxdb';
import { v4 as uuidv4 } from 'uuid';

/**
* Generate a UUID if the primary key is not set
*/
export function generateID(this: RxCollection, data: Record<string, any>) {
const primaryPath = this.schema.primaryPath;
const hasMetaData = this.schema.jsonSchema.properties.meta_data;
let metaUUID;

if (hasMetaData) {
data.meta_data = data.meta_data || [];
const meta = data.meta_data.find((meta: any) => meta.key === '_woocommerce_pos_uuid');
metaUUID = meta && meta.value;
}

if (!data[primaryPath] && metaUUID) {
data[primaryPath] = metaUUID;
} else if (!data[primaryPath]) {
const uuid = uuidv4();
//
if (primaryPath === 'uuid') {
data.uuid = uuid;
} else if (primaryPath === 'logId') {
data.logId = uuid;
} else if (primaryPath === 'localID') {
data.localID = uuid.slice(0, 8); // only short id required here
}
}

if (hasMetaData && !metaUUID) {
data.meta_data.push({
key: '_woocommerce_pos_uuid',
value: data[primaryPath],
});
}
}

export const RxDBGenerateIdPlugin: RxPlugin = {
name: 'generate-id',
rxdb: true,
prototypes: {
RxCollection: (proto: any) => {
proto.generateID = generateID;
},
},
overwritable: {},
hooks: {
createRxCollection: {
after({ collection }) {
collection.preInsert(generateID, false);
collection.preSave(generateID, false);
},
},
},
};
3 changes: 2 additions & 1 deletion tests/manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ describe('Manager', () => {
});

afterEach(() => {
jest.clearAllMocks();
storeDatabase.remove();
syncDatabase.remove();
manager.cancel();
jest.clearAllMocks();
});

describe('Query States', () => {
Expand Down
55 changes: 28 additions & 27 deletions tests/provider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';

import { render, cleanup } from '@testing-library/react';
import { render, cleanup, waitFor } from '@testing-library/react';
import { marbles } from 'rxjs-marbles/jest';

import httpClientMock from './__mocks__/http';
import { httpClientMock } from './__mocks__/http';
import { createStoreDatabase, createSyncDatabase } from './helpers/db';
import { QueryProvider, useQueryManager } from '../src/provider';
import { useQuery } from '../src/use-query';
Expand All @@ -21,11 +22,11 @@ describe('QueryProvider', () => {
syncDatabase = await createSyncDatabase();
});

afterEach(() => {
jest.clearAllMocks();
storeDatabase.remove();
syncDatabase.remove();
afterEach(async () => {
await storeDatabase.destroy();
await syncDatabase.destroy();
cleanup();
jest.clearAllMocks();
});

it('should provide a Manager instance', () => {
Expand All @@ -47,7 +48,7 @@ describe('QueryProvider', () => {
);
});

it('should create and retrieve a query instance', () => {
it('should create and retrieve a query instance', async () => {
// TestComponent1 uses useQuery to create a query
const TestComponent1 = () => {
const query = useQuery({ queryKeys: ['myQuery'], collectionName: 'products' });
Expand All @@ -74,34 +75,34 @@ describe('QueryProvider', () => {
<TestComponent2 />
</QueryProvider>
);

/**
* It's important to wait for the render to complete before making assertions.
* In this case the 'maybeCreateSearchDB' is created asynchronously and cleanup is called
* before the searchDB is created.
*
* @TODO - find a better way to handle this
*/
await waitFor(() => {
expect(true).toBe(true); // Placeholder to wait for render
});
});

it('should have initial values for query.params$ and query.result$', (done) => {
const TestComponent = () => {
const query = useQuery({ queryKeys: ['myQuery'], collectionName: 'products' });

const paramsPromise = new Promise((resolve) => {
const paramsSubscription = query?.params$.subscribe((params) => {
resolve(params);
paramsSubscription?.unsubscribe();
});
});

const dataPromise = new Promise((resolve) => {
const querySubscription = query?.result$.subscribe((data) => {
resolve(data);
querySubscription?.unsubscribe();
});
query.params$.subscribe((params) => {
expect(params).toEqual({});
});

Promise.all([paramsPromise, dataPromise]).then(([params, data]) => {
try {
expect(params).toEqual({}); // Expect params to be an empty object
expect(data).toEqual([]); // Expect data to be an empty array
done();
} catch (error) {
done(error);
}
query.result$.subscribe((result) => {
expect(result).toEqual(expect.objectContaining({
searchActive: false,
hits: [],
count: 0
}));
done();
});

return <div />;
Expand Down

0 comments on commit 9d6f449

Please sign in to comment.