Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
## 2.0.5 (2024-12-26)


### Bug Fixes

* **sdk:** sid logic (#43) (570c075)


### Features

* add array to track wish request (da7bc28)
* add autoSendPushToken argument (2826b68)
* add blank search method (#40) (ff10756)
* add cart method (7a0dd8b)
* add custom properties to track purchase request (d58f5af)
* add date check (a9b7069)
* add excluded_merchants property to search types (#46) (0912cee)
* add function to send token after app initialization (7a7bd29)
* add functions to get and push token sent date in storage (75e5ce0)
* add new action step to identify base commit in master (95930a9)
* add persona-synchronization.yaml (17ddf01)
* add types (ffec1d5)
* add types to functions (1cf1b75)
* BREAKING CHANGE use @notifee/react-native for requesting alarm permissions (0e23088)
* check application initialization (152e928)
* check image_url (#44) (0cfcfab)
* connect sdk to dev app (48788fa)
* move to sdk (85a1e1e)
* **sdk:** remove notifee (71e7e79)
* **sdk:** track mobile pushes (eadf5f4)
* swap auto-changelog for standard-changelog (ec88b46)



## 2.0.4 (2024-12-11)


Expand Down
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
10 changes: 10 additions & 0 deletions constants/search-keys.constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const SEARCH_QUERY_KEYS = [
'categories',
'locations',
'brands',
'colors',
'fashion_sizes',
'exclude',
'merchants',
'excluded_merchants'
]
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
preset: 'react-native',
transform: {
'^.+\\.js$': 'babel-jest',
},
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
transformIgnorePatterns: [
'node_modules/(?!(@react-native|react-native|@react-native-firebase|react-native-push-notification)/)',
],
};
92 changes: 92 additions & 0 deletions lib/tests/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import DataEncoder from "../utils";
import { SEARCH_QUERY_KEYS } from '../../constants/search-keys.constants';

describe('DataEncoder', () => {
let encoder;

beforeEach(() => {
encoder = new DataEncoder();
});

describe('encode', () => {
test('encodes a flat object into query string', () => {
const input = {
name: 'Ivan Ivanov',
age: 30,
city: 'New York',
};
const result = encoder.encode(input);
expect(result).toBe('name=Ivan%20Ivanov&age=30&city=New%20York');
});

test('encodes a nested object into query string', () => {
const input = {
user: {
name: 'Ivan',
details: {
age: 30,
city: 'New York',
},
},
};
const result = encoder.encode(input);
expect(result).toBe('user[name]=Ivan&user[details][age]=30&user[details][city]=New%20York');
});

test('encodes arrays correctly with SEARCH_QUERY_KEYS', () => {
const input = {
categories: ['category1', 'category2', 'category3'],
};
const result = encoder.encode(input);
expect(result).toBe('categories=category1,category2,category3');
});

test('encodes arrays correctly without SEARCH_QUERY_KEYS', () => {
const input = {
items: ['item1', 'item2'],
};
const result = encoder.encode(input);
expect(result).toBe('items[0]=item1&items[1]=item2');
});
});

describe('convertToObject', () => {
test('decodes a query string into an object', () => {
const input = 'name=John%20Doe&age=30&city=New%20York';
const result = encoder.convertToObject(input);
expect(result).toEqual({
name: 'John Doe',
age: '30',
city: 'New York',
});
});

test('handles nested query strings', () => {
const input = 'user[name]=John&user[details][age]=30&user[details][city]=New%20York';
const result = encoder.convertToObject(input);
expect(result).toEqual({
'user[name]': 'John',
'user[details][age]': '30',
'user[details][city]': 'New York',
});
});
});

describe('error cases', () => {
test('throws an error when actualKey is not set', () => {
const encoder = new DataEncoder();

encoder.actualKey = null;

expect(() => encoder.__dataEncoding(['item1', 'item2'])).toThrow(
'Directly passed array does not work'
);
});

test('returns null for non-object inputs', () => {
expect(encoder.encode(null)).toBeNull();
expect(encoder.encode(42)).toBeNull();
expect(encoder.encode('string')).toBeNull();
});
});
});
17 changes: 12 additions & 5 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SEARCH_QUERY_KEYS } from '../constants/search-keys.constants';

const DataEncoder = function () {
this.levels = [];
this.actualKey = null;
Expand Down Expand Up @@ -29,13 +31,18 @@ DataEncoder.prototype.__dataEncoding = function (data) {
} else if (is("Array", data)) {
if (!this.actualKey) throw new Error("Directly passed array does not work");

const aSize = data.length;
if (SEARCH_QUERY_KEYS.includes(this.actualKey)) {
finalString += uriPart + "=" + data.join(",") + "&";
} else {
const aSize = data.length;

for (let b = 0; b < aSize; b++) {
let aVal = data[b];
this.levels.push(b);
finalString += this.__dataEncoding(aVal);
for (let b = 0; b < aSize; b++) {
let aVal = data[b];
this.levels.push(b);
finalString += this.__dataEncoding(aVal);
}
}

} else {
finalString += uriPart + "=" + encodeURIComponent(data) + "&";
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@personaclick/rn-sdk",
"version": "2.0.4",
"version": "2.0.5",
"description": "PersonaClick React Native SDK",
"exports": {
".": "./index.js"
Expand Down
59 changes: 59 additions & 0 deletions tests/search.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import REES46 from '../index';

jest.mock('@react-native-community/push-notification-ios', () => {
});
jest.mock('react-native-device-info', () => {
});
jest.mock('@react-native-firebase/messaging', () => {
});
jest.mock('@react-native-async-storage/async-storage', () => {
});

describe('Search', () => {
let sdk;

beforeEach(() => {
sdk = new REES46('357382bf66ac0ce2f1722677c59511', 'android', true)
jest.spyOn(sdk, 'push').mockImplementation((callback) => {
callback();
});

jest.clearAllMocks();
});

afterEach(() => {
jest.clearAllMocks();
});

test('should call search with correct parameters for instant search and resolve', async () => {
const searchOptions = {type: 'instant_search', search_query: 'phone'};

const response = await sdk.search(searchOptions);

expect(response).toHaveProperty('categories');
expect(response).toHaveProperty('html');
expect(response).toHaveProperty('products');
expect(response).toHaveProperty('products_total');
});

test('should call search with correct parameters for full search and resolve', async () => {
const searchOptions = {type: 'full_search', search_query: 'coat'};

const response = await sdk.search(searchOptions);

expect(response).toHaveProperty('categories');
expect(response).toHaveProperty('html');
expect(response).toHaveProperty('products');
expect(response).toHaveProperty('products_total');
});

test('should return error when calling search with missing type parameter', async () => {
const searchOptions = {search_query: 'phone'};

try {
await sdk.search(searchOptions);
} catch (error) {
expect(error.message).toContain('Request failed with status code 400');
}
});
});
1 change: 1 addition & 0 deletions types/searchTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* @property {string} [exclude] - Comma separated list of products IDs to exclude from search results.
* @property {string} [email] - Email.
* @property {string} [merchants] - Comma separated list of merchants.
* @property {string} [excluded_merchants] - Comma separated list of excluded merchants.
* @property {string} [filters_search_by] - Available options for filter: name, quantity, popularity.
* @property {number} [brand_limit=1000] - Limits the number of brands in the response.
*/
Expand Down
Loading