From 1a3332dd88f15fe3f4b6db007cf463d0a7fbde4e Mon Sep 17 00:00:00 2001 From: Omar Luq <84993125+omarluq@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:24:45 -0600 Subject: [PATCH] I do not enjoy this commit --- README.md | 8 +++-- src/createStore.ts | 19 +++++++++-- src/store.ts | 4 +-- test/createStore.test.ts | 69 +++++++++++++++++++++++++++++++++++----- test/store.test.ts | 36 +++++++++++++-------- test/useStore.test.ts | 4 +-- 6 files changed, 110 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d6cf0bbd..9ce9f375 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/createStore.ts b/src/createStore.ts index 36efa769..ed30cb92 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -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"); @@ -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'); + } + } } \ No newline at end of file diff --git a/src/store.ts b/src/store.ts index 78dcf466..fd40fe9d 100644 --- a/src/store.ts +++ b/src/store.ts @@ -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); } /** diff --git a/test/createStore.test.ts b/test/createStore.test.ts index 65435265..60e69eee 100644 --- a/test/createStore.test.ts +++ b/test/createStore.test.ts @@ -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`); }); }); \ No newline at end of file diff --git a/test/store.test.ts b/test/store.test.ts index d55a5b26..f0155c75 100644 --- a/test/store.test.ts +++ b/test/store.test.ts @@ -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', () => { @@ -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}`); }); -}); +}); \ No newline at end of file diff --git a/test/useStore.test.ts b/test/useStore.test.ts index 3beac807..d3c8857e 100644 --- a/test/useStore.test.ts +++ b/test/useStore.test.ts @@ -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]