Skip to content

Commit

Permalink
Merge branch 'main' into rlamb/sc-255212/prototype-is-not-flag
Browse files Browse the repository at this point in the history
  • Loading branch information
kinyoklion authored Sep 5, 2024
2 parents 7ab4d5a + 4792391 commit fa6c4a7
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 1 deletion.
139 changes: 139 additions & 0 deletions packages/sdk/browser/__tests__/platform/LocalStorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import LocalStorage from '../../src/platform/LocalStorage';

it('can set values', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'setItem');

const storage = new LocalStorage(logger);
storage.set('test-key', 'test-value');
expect(spy).toHaveBeenCalledWith('test-key', 'test-value');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).not.toHaveBeenCalled();
});

it('can handle an error setting a value', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'setItem');
spy.mockImplementation(() => {
throw new Error('bad');
});

const storage = new LocalStorage(logger);
storage.set('test-key', 'test-value');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(
'Error setting key in localStorage: test-key, reason: Error: bad',
);
});

it('can get values', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'getItem');

const storage = new LocalStorage(logger);
storage.get('test-key');
expect(spy).toHaveBeenCalledWith('test-key');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).not.toHaveBeenCalled();
});

it('can handle an error getting a value', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'getItem');
spy.mockImplementation(() => {
throw new Error('bad');
});

const storage = new LocalStorage(logger);
storage.get('test-key');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(
'Error getting key from localStorage: test-key, reason: Error: bad',
);
});

it('can clear values', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'removeItem');

const storage = new LocalStorage(logger);
storage.clear('test-key');
expect(spy).toHaveBeenCalledWith('test-key');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).not.toHaveBeenCalled();
});

it('can handle an error clearing a value', async () => {
const logger = {
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
// Storage here needs to be the global browser 'Storage' not the interface
// for our platform.
const spy = jest.spyOn(Storage.prototype, 'removeItem');
spy.mockImplementation(() => {
throw new Error('bad');
});

const storage = new LocalStorage(logger);
storage.clear('test-key');

expect(logger.debug).not.toHaveBeenCalled();
expect(logger.info).not.toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
expect(logger.error).toHaveBeenCalledWith(
'Error clearing key from localStorage: test-key, reason: Error: bad',
);
});
12 changes: 12 additions & 0 deletions packages/sdk/browser/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

export default {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
transform: {
"^.+\\.tsx?$": "ts-jest"
// process `*.tsx` files with `ts-jest`
},
moduleNameMapper: {
'\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__ mocks __/fileMock.js',
},
}
3 changes: 2 additions & 1 deletion packages/sdk/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
},
"devDependencies": {
"@launchdarkly/private-js-mocks": "0.0.1",
"@testing-library/react": "^14.1.2",
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/jest": "^29.5.11",
"@typescript-eslint/eslint-plugin": "^6.20.0",
Expand All @@ -52,9 +51,11 @@
"eslint-plugin-jest": "^27.6.3",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "^3.0.0",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typedoc": "0.25.0",
"typescript": "^5.5.3",
"vite": "^5.4.1",
Expand Down
22 changes: 22 additions & 0 deletions packages/sdk/browser/src/platform/BrowserPlatform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
LDOptions,
Storage,
/* platform */
} from '@launchdarkly/js-client-sdk-common';

import LocalStorage, { isLocalStorageSupported } from './LocalStorage';

export default class BrowserPlatform /* implements platform.Platform */ {
// encoding?: Encoding;
// info: Info;
// fileSystem?: Filesystem;
// crypto: Crypto;
// requests: Requests;
storage?: Storage;

constructor(options: LDOptions) {
if (isLocalStorageSupported()) {
this.storage = new LocalStorage(options.logger);
}
}
}
42 changes: 42 additions & 0 deletions packages/sdk/browser/src/platform/LocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { LDLogger, Storage } from '@launchdarkly/js-client-sdk-common';

export function isLocalStorageSupported() {
// Checking a symbol using typeof is safe, but directly accessing a symbol
// which is not defined would be an error.
return typeof localStorage !== 'undefined';
}

/**
* Implementation of Storage using localStorage for the browser.
*
* The Storage API is async, and localStorage is synchronous. This is fine,
* and none of the methods need to internally await their operations.
*/
export default class PlatformStorage implements Storage {
constructor(private readonly logger?: LDLogger) {}
async clear(key: string): Promise<void> {
try {
localStorage.removeItem(key);
} catch (error) {
this.logger?.error(`Error clearing key from localStorage: ${key}, reason: ${error}`);
}
}

async get(key: string): Promise<string | null> {
try {
const value = localStorage.getItem(key);
return value ?? null;
} catch (error) {
this.logger?.error(`Error getting key from localStorage: ${key}, reason: ${error}`);
return null;
}
}

async set(key: string, value: string): Promise<void> {
try {
localStorage.setItem(key, value);
} catch (error) {
this.logger?.error(`Error setting key in localStorage: ${key}, reason: ${error}`);
}
}
}

0 comments on commit fa6c4a7

Please sign in to comment.