Skip to content

Commit

Permalink
I do not enjoy this commit
Browse files Browse the repository at this point in the history
  • Loading branch information
omarluq committed Nov 24, 2023
1 parent b39dcfc commit 1a3332d
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 30 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ Let's dive into a simple use case to see how Stimulus Store works. In this examp
```

```js
// controllers/stores/counter.js
// controllers/stores.js
import { createStore } from "stimulus-store";

export const counterStore = createStore(name: 'counterStore', initialValue: 0, type: Number)
export let counterStore;

(async function initializeStores() {
counterStore = await createStore({ name: 'counterStore', initialValue: 0, type: Number });
})();
```

```js
Expand Down
19 changes: 17 additions & 2 deletions src/createStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Store } from './store';
import type { StoreOptions } from './storeOptions';
import { typeMap } from './storeValuesTypeMap';

export function createStore<T>(options: StoreOptions<T>): Store<T> {
export async function createStore<T>(options: StoreOptions<T>): Promise<Store<T>>{
const { name, type, initialValue } = options;
if (typeof initialValue === "undefined") {
throw new Error("Store must be initialized with a value");
Expand All @@ -45,5 +45,20 @@ export function createStore<T>(options: StoreOptions<T>): Store<T> {
throw new Error(`Invalid type: ${type?.name}`);
}

return new Store<T>(symbolName, initialValue, type);
// return new Store<T>(symbolName,initialValue, type);

const store: Store<T> = new Store<T>(symbolName, type);
try {
await store.set(initialValue);
return store;
} catch (error) {
// Assert that error is an instance of Error
if (error instanceof Error) {
// Handle the error here. For example, you can re-throw it with a more meaningful message:
throw new Error(`Failed to create store: ${error.message}`);
} else {
// If error is not an instance of Error, you can throw a generic error or handle it differently
throw new Error('An unknown error occurred while creating the store');
}
}
}
4 changes: 1 addition & 3 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@ export class Store<T> {
* Creates a new store.
*
* @param {symbol} name - The name of the store.
* @param {T} initialValue - The initial value of the store.
* @param {new (...args: unknown[]) => unknown} type - The type of the store's value.
*/
constructor(name: symbol, initialValue: T, type: new (...args: unknown[]) => unknown) {
constructor(name: symbol, type: new (...args: unknown[]) => unknown) {
this.name = name;
this.subscribers = new Set();
this.type = type;
this.set(initialValue);
}

/**
Expand Down
69 changes: 61 additions & 8 deletions test/createStore.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,75 @@
import { createStore } from '../src/createStore';

describe('createStore', () => {
it('should throw an error if no initial value is provided', () => {
expect(() => createStore({ name: 'testStore', type: Number } as any)).toThrow('Store must be initialized with a value');
it('should throw an error if no initial value is provided', async () => {
await expect(createStore({ name: 'testStore', type: Number } as any))
.rejects.toThrow('Store must be initialized with a value');
});

it('should throw an error if no name is provided', () => {
expect(() => createStore({ initialValue: 0, type: Number } as any)).toThrow('Store name must be of Type string');
it('should throw an error if no initial name is provided', async () => {
await expect(createStore({ initialValue: 0, type: Number } as any))
.rejects.toThrow('Store name must be of Type string');
});

it('should create a new store with the provided name, initial value, and type', () => {
const store = createStore({ name: 'testStore', initialValue: 0, type: Number });
it('should create a new store with the provided name, initial value, and type', async () => {
const store = await createStore({ name: 'testStore', initialValue: 0, type: Number });
expect(store.get()).toBe(0);
expect(typeof store.get()).toBe('number');
expect(store.name.toString()).toBe('Symbol(testStore)');
});

it('should throw an error if an invalid type is provided', () => {
expect(() => createStore({ name: 'testStore', initialValue: 0, type: Set } as any)).toThrow('Invalid type: Set');
it('should throw an error if an invalid type is provided', async () => {
await expect(createStore({ name: 'testStore', type: Set, initialValue: 0 } as any))
.rejects.toThrow('Invalid type: Set');
});

it('should create a store with a number type', async () => {
const store = await createStore({ name: 'testStore', initialValue: 0, type: Number });
expect(typeof store.get()).toBe('number');
});

it('should create a store with a string type', async () => {
const store = await createStore({ name: 'testStore', initialValue: 'test', type: String });
expect(typeof store.get()).toBe('string');
});

it('should create a store with a boolean type', async () => {
const store = await createStore({ name: 'testStore', initialValue: true, type: Boolean });
expect(typeof store.get()).toBe('boolean');
});

it('should create a store with an array type', async () => {
const store = await createStore({ name: 'testStore', initialValue: [1, 2, 3], type: Array });
expect(Array.isArray(store.get())).toBe(true);
});

it('should create a store with an object type', async () => {
const store = await createStore({ name: 'testStore', initialValue: { key: 'value' }, type: Object });
expect(typeof store.get()).toBe('object');
});

it('should throw an error when createStore is called with an initialValue that does not match the number type', async () => {
await expect(createStore({ name: 'testStore', initialValue: 'not a number', type: Number }))
.rejects.toThrow(`Failed to create store: Value 'not a number' must be of type Number`);
});

it('should throw an error when createStore is called with an initialValue that does not match the string type', async () => {
await expect(createStore({ name: 'testStore', initialValue: 123, type: String }))
.rejects.toThrow(`Failed to create store: Value '123' must be of type String`);
});

it('should throw an error when createStore is called with an initialValue that does not match the boolean type', async () => {
await expect(createStore({ name: 'testStore', initialValue: 'not a boolean', type: Boolean }))
.rejects.toThrow(`Failed to create store: Value 'not a boolean' must be of type Boolean`);
});

it('should throw an error when createStore is called with an initialValue that does not match the array type', async () => {
await expect(createStore({ name: 'testStore', initialValue: 'not an array', type: Array }))
.rejects.toThrow(`Failed to create store: Value 'not an array' must be of type Array`);
});

it('should throw an error when createStore is called with an initialValue that does not match the object type', async () => {
await expect(createStore({ name: 'testStore', initialValue: 'not an object', type: Object }))
.rejects.toThrow(`Failed to create store: Value 'not an object' must be of type Object`);
});
});
36 changes: 23 additions & 13 deletions test/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ describe('Store', () => {
let store: Store<number>;

beforeEach(() => {
store = new Store(Symbol('testStore'), 0, Number);
store = new Store(Symbol('testStore'), Number);
});

it('should initialize with the correct value', () => {
expect(store.get()).toBe(0);
expect(store.get()).toBeUndefined();
});

it('should update the value correctly', () => {
Expand All @@ -33,32 +33,42 @@ describe('Store', () => {
expect(mockCallback).not.toHaveBeenCalledWith(15);
});

it('should not notify subscribers when value is the same', () => {
it('should not notify subscribers when value is the same', async () => {
const mockCallback = jest.fn();

// First set: initial value
await store.set(0);

// Subscribe to the store and invoke the callback
store.subscribe(mockCallback);

// Set the value to the same value
// called only once cause the callback is invoked on subscription as well
store.set(0);
expect(mockCallback).not.toHaveBeenCalledWith(1);

// Second set: change value to 1 and invoke the callback
await store.set(1);

// Third set: attempt to set the same value (1), ignore the callback
await store.set(1);

// Expect the callback to have been called twice: once for the subscription, and once for the second set
expect(mockCallback).toHaveBeenCalledTimes(2);
});

it('should not notify subscribers when filter returns false', () => {
it('should not notify subscribers when filter returns false', async () => {
const mockCallback = jest.fn();
store.subscribe(mockCallback);

store.set(20, { filter: () => false });
await store.set(20, { filter: () => false });
expect(mockCallback).not.toHaveBeenCalledWith(20);
});

it('should call the callback with the current value when a function is passed to set', () => {
const callback = jest.fn().mockImplementation(currentValue => currentValue + 10);
store.set(0);
store.set(callback);
expect(callback).toHaveBeenCalledWith(0);
expect(store.get()).toBe(10);
});

it('should throw an error when setting a value of the wrong type', async () => {
await expect(Promise.resolve(store.set('wrong type' as any))).rejects.toThrow(`Value 'wrong type' must be of type ${Number.name}`);
await expect(store.set('wrong type' as any)).rejects.toThrow(`Value 'wrong type' must be of type ${Number.name}`);
});
});
});
4 changes: 2 additions & 2 deletions test/useStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ describe('useStore', () => {
let mockController: StoreController<any>;
let testStore: Store<any>;

beforeEach(() => {
testStore = createStore({ name: 'testStore', type: Number, initialValue: 0 });
beforeEach(async () => {
testStore = await createStore({ name: 'testStore', type: Number, initialValue: 0 });
mockController = {
constructor: {
stores: [testStore]
Expand Down

0 comments on commit 1a3332d

Please sign in to comment.