diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..b7fceb10 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +test/ +public/ +dist/ +global-types.d.ts +*.config.js +*.config.json +*.config.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..96ec2a0d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [ + { + "env": { + "node": true + }, + "files": [ + ".eslintrc.{js,cjs}" + ], + "parserOptions": { + "sourceType": "script" + } + } + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + } +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1d7e185..dce4bb94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,4 +12,14 @@ jobs: with: node-version: 21 - run: yarn install --frozen-lockfile - - run: yarn test \ No newline at end of file + - name: Build + run: yarn build + - name: Lint + run: yarn lint + - name: Test + run: yarn test + - name: Upload test report + uses: actions/upload-artifact@v3 + with: + name: test-report + path: ./.jest-stare/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 69fcecb0..47905684 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ dist-ssr *.sln *.sw? package-lock.json -yarn.lock \ No newline at end of file +yarn.lock +results.xml +.jest-stare \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 272f8df1..9283cb52 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['/test/**/*.test.ts'], + reporters: [ + "default" + ] }; \ No newline at end of file diff --git a/package.json b/package.json index 371e8adb..4f3ec308 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,28 @@ { "name": "stimulus-store", - "private": true, - "version": "0.0.0", + "version": "0.0.1-beta", + "description": "Lightweight state management for Stimulus.js", + "main": "dist/stimulus-store.js", "scripts": { "build": "rollup -c --bundleConfigAsCjs", - "test": "jest" + "test": "jest --reporters default jest-stare", + "lint": "eslint .", + "lint:fix": "eslint . --fix" }, + "keywords": [ + "stimulus", + "store" + ], + "author": "", + "license": "ISC", "devDependencies": { "@hotwired/stimulus": "^3.2.2", "@types/jest": "^29.5.10", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", + "eslint": "^8.54.0", "jest": "^29.7.0", + "jest-stare": "^2.5.1", "prettier": "^3.1.0", "rollup": "^4.5.1", "rollup-plugin-commonjs": "^10.1.0", @@ -17,5 +30,9 @@ "rollup-plugin-typescript2": "^0.36.0", "ts-jest": "^29.1.1", "typescript": "^5.2.2" - } + }, + "jest-stare": { + "resultDir": ".jest-stare" + }, + "private": false } diff --git a/report.json b/report.json new file mode 100644 index 00000000..a52383fe --- /dev/null +++ b/report.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":3,"numPassedTests":21,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":3,"numTotalTests":21,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1700710936337,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["camelize"],"duration":1,"failureDetails":[],"failureMessages":[],"fullName":"camelize should correctly camelize testString","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should correctly camelize testString"},{"ancestorTitles":["camelize"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"camelize should correctly camelize TestString","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should correctly camelize TestString"},{"ancestorTitles":["camelize"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"camelize should correctly camelize testString","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should correctly camelize testString"},{"ancestorTitles":["camelize"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"camelize should correctly camelize TestString","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should correctly camelize TestString"}],"endTime":1700710936966,"message":"","name":"/Users/oluqman/Desktop/dev/stimulus-store/test/camelize.test.ts","startTime":1700710936363,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["useStore"],"duration":1,"failureDetails":[],"failureMessages":[],"fullName":"useStore should subscribe to stores and call update methods on value changes","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should subscribe to stores and call update methods on value changes"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should allow direct access to store values on the controller","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should allow direct access to store values on the controller"},{"ancestorTitles":["useStore"],"duration":1,"failureDetails":[],"failureMessages":[],"fullName":"useStore should allow direct access to store instances on the controller","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should allow direct access to store instances on the controller"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should clean up subscriptions when controller disconnects","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should clean up subscriptions when controller disconnects"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should add a getter for the store value to the controller","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should add a getter for the store value to the controller"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should add a getter for the store instance to the controller","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should add a getter for the store instance to the controller"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should add an update method to the controller","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should add an update method to the controller"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should notify each controller when the shared store's value is updated by any controller","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"status":"passed","title":"should notify each controller when the shared store's value is updated by any controller"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should call the callback with the current value when a function is passed to set","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"status":"passed","title":"should call the callback with the current value when a function is passed to set"},{"ancestorTitles":["useStore"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"useStore should update the store's value with the return value of the callback","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should update the store's value with the return value of the callback"}],"endTime":1700710937008,"message":"","name":"/Users/oluqman/Desktop/dev/stimulus-store/test/useStore.test.ts","startTime":1700710936969,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":["Store"],"duration":1,"failureDetails":[],"failureMessages":[],"fullName":"Store should initialize with the correct value","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should initialize with the correct value"},{"ancestorTitles":["Store"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"Store should update the value correctly","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should update the value correctly"},{"ancestorTitles":["Store"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"Store should notify subscribers when value changes","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should notify subscribers when value changes"},{"ancestorTitles":["Store"],"duration":1,"failureDetails":[],"failureMessages":[],"fullName":"Store should stop notifying unsubscribed callbacks","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should stop notifying unsubscribed callbacks"},{"ancestorTitles":["Store"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"Store should not notify subscribers when value is the same","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should not notify subscribers when value is the same"},{"ancestorTitles":["Store"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"Store should not notify subscribers when filter returns false","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"status":"passed","title":"should not notify subscribers when filter returns false"},{"ancestorTitles":["Store"],"duration":0,"failureDetails":[],"failureMessages":[],"fullName":"Store should call the callback with the current value when a function is passed to set","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"status":"passed","title":"should call the callback with the current value when a function is passed to set"}],"endTime":1700710937052,"message":"","name":"/Users/oluqman/Desktop/dev/stimulus-store/test/store.test.ts","startTime":1700710937015,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/src/store.ts b/src/store.ts index a7ea201d..194ef1f4 100644 --- a/src/store.ts +++ b/src/store.ts @@ -65,7 +65,7 @@ export class Store { private notifySubscribers(options: NotifySubscriberOptions) { Array.from(this.subscribers) - .filter(_ => options.filter(this.value)) + .filter(() => options.filter(this.value)) .forEach(callback => callback(this.value)) } } diff --git a/src/storeController.ts b/src/storeController.ts index 0b639fa3..d3ba7794 100644 --- a/src/storeController.ts +++ b/src/storeController.ts @@ -1,9 +1,10 @@ import type { Controller } from "@hotwired/stimulus" import type { Store } from './store'; -export interface StoreController extends Controller { +export interface StoreController extends Controller { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; constructor: { - stores?: Store[]; + stores?: Store[]; }; } \ No newline at end of file diff --git a/src/useStore.ts b/src/useStore.ts index 49f92957..28aa6f49 100644 --- a/src/useStore.ts +++ b/src/useStore.ts @@ -42,18 +42,18 @@ import type { Store } from './store'; import type { StoreController } from './storeController'; // Adjust the path as needed import { camelize } from './utils/camelize'; -export function useStore(controller: StoreController) { - const stores: Store[] = controller.constructor.stores || []; +export function useStore(controller: StoreController) { + const stores: Store[] = controller.constructor.stores || []; const unsubscribeFunctions: (() => void)[] = []; stores.forEach((store) => { const storeName: string = store.name; const camelizedName = camelize(storeName); const onStoreUpdateMethodName = `on${camelize(storeName, true)}Update`; - const onStoreUpdateMethod: (value: any) => void = controller[onStoreUpdateMethodName]; + const onStoreUpdateMethod = controller[onStoreUpdateMethodName] as (value: T) => void; if (onStoreUpdateMethod) { - const updateMethod: (value: any) => void = value => { + const updateMethod: (value: T) => void = value => { onStoreUpdateMethod.call(controller, value); }; diff --git a/test/useStore.test.ts b/test/useStore.test.ts index 0579c719..9877a24e 100644 --- a/test/useStore.test.ts +++ b/test/useStore.test.ts @@ -3,7 +3,7 @@ import { useStore } from '../src/useStore'; import type { StoreController } from '../src/storeController'; describe('useStore', () => { - let mockController: StoreController; + let mockController: StoreController; let testStore: Store; beforeEach(() => { @@ -19,7 +19,7 @@ describe('useStore', () => { scope: jest.fn(), element: jest.fn(), // Add the other missing properties here... - } as unknown as StoreController; + } as unknown as StoreController; useStore(mockController); }); @@ -72,7 +72,7 @@ describe('useStore', () => { scope: jest.fn(), element: jest.fn(), // Add the other missing properties here... - } as unknown as StoreController; + } as unknown as StoreController; // Assume that the controllers have a method to update the store's value useStore(mockController2); mockController.updateTestStore = (value: any) => testStore.set(value);